Tutorial

Class และ Instance Variable บน Python มันต่างกันยังไง ?

By Arnon Puitrakul - 14 กันยายน 2021

Class และ Instance Variable บน Python มันต่างกันยังไง ?

ก่อนหน้านี้ที่เขียนเรื่องการ Clone Object ไป มา ๆ โผล่ ๆ ของตัวแปรต่าง ๆ ทำให้เรานึกถึงอีกพฤติกรรมนึงที่หลาย ๆ คนอาจจะคิดไม่ถึงบนภาษา Python โดยเฉพาะเมื่อเราเขียนโปรแกรมแบบ OOP บน Python เป็นเรื่องที่เมื่อก่อนเราเองก็ไม่รู้มาก่อนเลย พอมารู้ก็คือ ห่ะ เหรอ แบบนี้ก็ได้เหรอฟร๊ะ นั่นคือ Class Variable และ Instance Variable

ส่วนประกอบของ Class

โดยปกติตอนที่เราเรียน OOP กัน เราจะรู้ว่า ใน Class เราจะกำหนดของอยู่ 2 อย่างใหญ่ ๆ คือ Attribute และ Method (Constructor เป็น Method พิเศษตัวนึงเนอะ) ซึ่งตอนก่อนหน้าที่เราคุยเรื่องของ Decorator บน Class เราพูดถึง Method ไปแล้ว วันนี้เรามาโฟกัสที่ Attribute กัน

Attribute เป็นส่วนนึงของ Class ที่ใช้ในการบอกลักษณะต่าง ๆ ของ Class ตัวอย่างเช่น ถ้าเป็น รถ ก็จะเป็นจำนวนล้อ สี รุ่น ยี่ห้อ ต่าง ๆ ที่ใช้บ่งลักษณะของรถได้ ซึ่งโดยปกติแล้ว ส่วนของ Attribute เราจะทำการ Encapsulate เพื่อไม่ให้คนอื่นเข้าถึง หรือ แก้ไข Attribute ใน Object โดยตรง และ ข้อมูลใน Attribute จะเป็นของ Instance นั้น ๆ เลย ไม่ได้ใช้งานรวมกัน แต่เราอาจจะกำหนด Default ได้

การประกาศ Attribute ใน Python

from dataclass import dataclass

@dataclass
class Car :
    brand: str
    model: str
    colour: str
    no_wheel: int
    

เวลาเราจะประกาศ Attribute ใน Python เราสามารถทำได้หลายวิธีมาก ๆ เช่น Code ด้านบน เรากำหนดไว้ที่ด้านบนของ Class ได้เลย

class Car :
    def __init__ (brand, model, colour, no_wheel) :
        self.brand = brand
        self.model = model
        self.colour = colour
        self.no_wheel = no_wheel

อีกวิธีคือ เรากำหนดมันใน Constructor ก็ได้เหมือนกัน ในการใช้งานจริง มองเผิน ๆ การใช้งานไม่ต่างกันเลยนะ เราสามารถเข้าถึง และ แก้ไข Attribute ที่เรากำหนดได้หมดเลย หรือสนุกกว่านั้นอีก เราสามารถใช้ทั้ง 2 วิธีพร้อม ๆ กันเลยก็ได้นะ ถ้าสดชื่น จะเห็นได้เลยว่า Python เอง มันให้อิสระกับการเขียนของเรามาก เราจะเขียนแทบจะยังไงก็ได้แล้ว แต่จริง ๆ แล้วจะบอกว่า ไม่ว่าเราจะเขียนวิธีนี้ใน 2 วิธีนี้ มันมีความแตกต่างกันอยู่ด้วย

Instance Variable

class Car :
    def __init__ (brand, model, colour, no_wheel) :
        self.brand = brand
        self.model = model
        self.colour = colour
        self.no_wheel = no_wheel

มาเริ่มที่ตัวแรก ตัวที่เราน่าจะคุ้นเคยกันที่สุดก่อน เป็น Variable ที่เรากำหนดเป็น Attribute ภายใน Object เช่นตัวอย่างด้านบนนี้ เราจะลองเอามาสร้างเป็น Object กัน

car_a = Car('Honda', 'Civic', 'Red', 4)
car_b = Car('Honda', 'City', 'Red', 4)

เราเอา Class Car มาสร้างเป็น Object 2 ตัวด้วยกัน โดยที่เรากำหนด Attribute ที่ไม่เหมือนกันตัวนึงคือ Model แต่อันที่เราสนใจ จะเป็นตัวที่เหมือนกันดีกว่า เราลองมาเปลี่ยนสี car_b เป็น White ดูว่าจะเป็นยังไง

car_b.colour = 'White'
print('a', car_a.colour)
print('b', car_b.colour)
'Red'
'White'

เราจะเห็นเลยว่า ถ้าเราเปลี่ยนสีของ car_b ค่าสีของ car_a ไม่เปลี่ยนตามไปด้วย เพราะ car_a และ car_b เป็นคนละ Instance กันสิ่งที่เหมือนกันมีแค่มันสร้างมาจาก Class เดียวกันเท่านั้น ทำให้เราเรียก Variable ที่ผูกตาม Instance ว่า Instance Variable ตามชื่อเลย

Class Variable

from dataclass import dataclass

@dataclass
class Car :
    brand: str = 'Brand'
    model: str = 'Model'
    colour: str = 'Unknown'
    no_wheel: int = 4

มาลองดูอีกตัวคือ Class Variable ตัวอย่างแรกที่เรากำหนดไว้เลย คือเรากำหนดมันเอาไว้บน Class เลย ทำให้เราสามารถเข้าถึงได้ผ่าน Scope ภายใน Class ที่ย่อมลึกกว่านี้อยู่แล้ว เรามาลองอะไรสนุก ๆ กันดีกว่า เริ่มจากเราจะแก้ Code ด้านบนให้มันมี Default Value ก่อน

print(Car.no_wheel)
4

และ เราลองไม่ต้องเอามาสร้างเป็น Object เราจะเรียก Attribute ผ่าน Class เลย ปรากฏว่า เห้ย เรียกได้เฉยเลย ค่าออกตรงด้วย ไม่มี Warning หรือ Error ใด ๆ ทั้งสิ้น

from dataclass import dataclass

@dataclass
class Car :
    brand: str = 'Brand'
    model: str = 'Model'
    no_wheel: int = 4
    
    def __init__ (self, colour) :
        self.colour = colour

เพื่อให้เห็นพฤติกรรมที่แปลก เราขอสร้าง Instance Variable เพิ่มเข้าไปเพื่อให้เห็นความแตกต่างละกัน

car_a = Car('Red')
car_b = Car('White')

เราสร้าง Object จาก Class Car ทั้งคู่ โดยการกำหนดสีของรถให้แตกต่างกัน เป็นสีแดง และ สีขาว จากก่อนหน้า เราจะคิดว่า Variable ใน Class และ Object หรือ Attribute มันควรจะเป็นของใครของมันแยกเป็น Instance ไป งั้นเรามาลอง ทำอะไรสนุก ๆ ดีกว่า

Car.no_wheel = 6
print('a', car_a.no.wheel)
print('b', car_b.no.wheel)
'a', 6
'b', 6

เห๋ ทำไมละ ทำไมออก 6 ละ ทั้ง ๆ ที่เราแก้ ที่ Car และ Object เราสร้างออกมาก่อนหน้าที่เราจะกำหนด no_wheel ใหม่ซะอีก ทำไมมันไป Update ที่ car_a และ car_b ด้วยละ สาเหตุนั่นเป็นเพราะ ทั้ง 2 Object นี้ถูกสร้างมาจาก Class Car และ Variable ตัวนี้ มันถูกกำหนดอยู่ใน Class ทำให้จริง ๆ แล้ว ตอนที่มันถูกสร้างมาเป็น Object ค่ามันไม่ได้ถูก Clone มาด้วย แต่มันเป็นการชี้ไปหาตัว Car เอง  ทำให้ค่าที่มันเปลี่ยนตาม Class เราจะเรียกพวกนี้ว่า Class Variable

โดยสรุปคือ Class Variable จะแตกต่างจาก Instance Variable อยู่ 2 เรื่องใหญ่ ๆ คือ Class Variable ไม่จำเป็นต้องสร้างผ่าน Constructor มันถูกกำหนดตั้งแต่เราสร้าง Class แล้ว ทำให้เราสามารถเข้าถึง และ แก้ไข ตั้งแต่มันยังไม่เป็น Object ได้เลย และถึงแม้ว่า เราจะสร้างออกไปเป็น Instance แล้ว Python จะไม่ได้ Clone ค่าออกมาแล้ว Assign ให้แต่ละ Instance เลย แต่มันจะใช้เป็นการชี้กลับไปที่ Class ตรง ๆ เลย นั่นทำให้เมื่อเราเปลี่ยนที่ Class ทุก Instance ที่ถูกสร้างจาก Class นั้น ๆ ก็จะโดนเปลี่ยนไปด้วย

car_a.no_wheel = 4
print('a', car_a.no.wheel)
print('b', car_b.no.wheel)
'a', 4
'b', 6

แต่ ๆ ที่เราเล่ามาใน Class Variable มันจะเป็นจริงแค่ขาเดียวเท่านั้น ถ้าเราลองกลับกันละ แทนที่เราจะกำหนดที่ Car โดยตรง เราไปกำหนดที่ Instance นั้น ๆ เลยละ มันจะเกิดอะไรขึ้น สิ่งที่มันเป็นคือ มันก็จะเปลี่ยนแค่ที่ Instance นั้น ๆ ที่เดียวเลย ไม่ได้เปลี่ยนทั้ง Car นั่นเป็นสาเหตุที่เวลาเราเขียน Class หลาย ๆ ครั้ง เราจะพบว่าการเขียนด้วย 2 วิธี เราจะเขียนแบบไหนก็ได้ เพราะส่วนใหญ่แล้ว เวลาเรากำหนด Attribute ไว้ด้านบน และ มาเติม ค่าผ่าน Constructor อยู่ดี ทำให้ค่ามันถูก Update ไป ก็จะเหมือนตัวอย่างที่เรายกให้ดูอันนี้นั่นเอง

ทำไมมันต้องมาแยก มันมีประโยชน์อะไร ?

ถ้าอ่านผ่าน ๆ อาจจะมองว่า อะไรของแกฟร๊ะ รู้ไปแล้วมันได้ประโยชน์อะไร สุดท้ายแล้ว เราก็แยกออกมาเป็น Instance อยู่ดี แต่เรามองว่า เราสามารถใช้ความสามารถนี้ในลดความซ้ำซ้อนของ Code เราได้เป็นอย่างดีเลยนะ ยิ่งถ้าเรามานั่งดูส่วนไหนที่เชื่อมกัน ส่วนไหนที่ไม่เชื่อมกัน แล้วจัดระเบียบมันตามการใช้งาน มันลดจำนวน Code ที่เราต้องเขียนมหาศาลมาก ๆ ตาม Concept DRY (Don't Repeat Yourselves) ที่เขาแนะนำว่า เราควรจะลดการเขียนส่วนที่ซ้ำกันด้วยการทำ Abstraction และ Data Normailsation

Read Next...

การสร้าง SSD Storage Pool บน Synology DSM

การสร้าง SSD Storage Pool บน Synology DSM

สำหรับคนที่ใช้ Synology NAS บางรุ่นจะมีช่อง M.2 สำหรับเสียบ NVMe SSD โดยพื้นฐาน Synology บอกว่ามันสำหรับการทำ Cache แต่ถ้าเราต้องการเอามันมาทำเป็น Storage ละ มันจะทำได้มั้ย วันนี้เราจะมาเล่าวิธีการทำกัน...

Multiprogramming, Multiprocessing และ Multithreading

Multiprogramming, Multiprocessing และ Multithreading

หลังจากที่เรามาเล่าเรื่อง malloc() มีคนอยากให้มาเล่าเรื่อง pthread เพื่อให้สามารถยัด Content ที่ละเอียด และเข้าใจง่ายในเวลาที่ไม่นานเกินไป เลยจะมาเล่าพื้นฐานที่สำคัญของคำ 3 คำคือ Multiprogramming, Multitasking, Multiprocessing และ Multithreading...

Synology NAS และ SSD Cache จำเป็นจริง ๆ เหรอ เหมาะกับระบบแบบใด

Synology NAS และ SSD Cache จำเป็นจริง ๆ เหรอ เหมาะกับระบบแบบใด

ใน Synology NAS มีความสามารถนึงที่น่าสนใจคือ การใช้ SSD เป็น Cache สำหรับระบบ ที่ทำให้ Performance ในการอ่านเขียน เร็วขึ้นกว่าเดิมมาก ๆ แน่นอนว่า เราลองละ วันนี้เราจะมาเล่าให้อ่านกันว่า หากใครคิดที่จะทำ มันเหมาะ หรือ ไม่เหมาะกับการใช้งานของเรา...

ฮาวทูย้าย Synology Add-on Package ไปอีก Volume

ฮาวทูย้าย Synology Add-on Package ไปอีก Volume

เรื่องราวเกิดจากการที่เราต้องย้าย Add-on Package ใน DSM และคิดว่าหลาย ๆ คนน่าจะต้องประสบเรื่องราวคล้าย ๆ กัน วันนี้เราจะมาเล่าวิธีการว่า เราทำยังไง เจอปัญหาอะไร และ แก้ปัญหาอย่างไรให้ได้อ่านกัน...