รวม 3 Decorator ที่ใช้บ่อยใน Python

ตอนก่อน เรามาเล่าเรื่อง Decorator ใน Python กันไปแล้ว มาในตอนนี้เราอยากจะมาเล่าถึง 3 Decorator ที่เราใช้บ่อย ๆ ส่วนใหญ่จะเป็น Snippet เขียนเองทั้งนั้น วันนี้มาดูกันว่าจะมี Decorator ตัวไหนบ้าง

timeme

ตัวแรกที่เราใช้บ่อยมาก ๆ คือการวัดเวลาในการทำงานของส่วนต่าง ๆ ของ Code โดยเฉพาะช่วงทำ Thesis อยากรู้ว่า Function มันใช้เวลาเท่าไหร่ เลยลองเขียนเป็น Decorator ง่าย ๆ ขึ้นมาตัวนึง เพื่อวัดเวลา

import time
import functools

def timeme (func) :
    
    @functools.wraps(func)
    def measure_time (*args,**kwargs) :
        start_time = time.time()
        result = func(*args,**kwargs)
        elapsed = time.time() - start_time
        
        print(func.__name__, 'ran for', elapsed, 'sec(s)')
        return result        

    return measure_time

@timeme
def cal_result (max_num) :
    result = 0
    for num in range(max_num) :
        result += num

cal_result(10000000)

เราสร้างเป็น Wrapper Function ง่าย ๆ เอา ส่วนของตัววัดเวลามาคล่อมเอาเลย ทำให้เราได้เวลาในการรัน Function นั้น ๆ ออกมาได้เลย หรือจริง ๆ แล้ว ถ้าเราไม่อยากให้มัน Print ออกมา เมื่อ Function รันเสร็จ เราอาจจะเก็บใส่ไว้ใน Decorator แล้วเราก็ค่อยเรียกออกมาเวลาที่เราต้องการก็ทำได้เหมือนกัน แต่ปกติเวลาเราใช้งาน เราจะใส่เพื่อ Test เฉย ๆ เลยให้มัน Print ออกมาเลย จะได้ง่าย ๆ

dataclass

อันถัดไปใช้บ่อยไม่แพ้กันคือ dataclass ตัวนี้เราไม่ต้องเขียนเอง เพราะมันมีมากับ Python แล้ว ก่อนจะเข้าเรื่องของ Decorator นี้ เราจะต้องเกริ่นก่อนว่า เวลาเราเขียน Class เราจะต้องเขียนสิ่งที่เรียกว่า Constructor ซึ่งถ้าเราจะต้องทำอะไรในนั้นนอกจาก การ Init พวก Attribute ต่าง ๆ เขียนเองมันก็โอเคแหละ แต่ถ้าเราทำแค่นั้นละ เขียนแค่นั้นทุกครั้งมันก็น่าเบื่อแย่เลย dataclass เข้ามาช่วยแก้ปัญหาเรื่องนี้ได้เป็นอย่างดีเลย

from dataclass import dataclass

@dataclass
class Person:
    name: str
    surname: str
    age: int = 1
        
    def full_name(self) -> str:
        return name + ' ' + surname

thomas = Person('Thomas', 'Edison', 84)

จะเห็นว่า ใน Class Person เราไม่ได้เขียน Constructor เลย แต่ตอนที่เราเอา Class ออกมาสร้างเป็น Object เราใช้ Constructor ที่ใส่ของเข้าไป 3 อย่าง ซึ่งเราไม่ได้ประกาศเอง เป็นสิ่งที่ dataclass มันทำให้เราเอง จะเห็นได้เลยว่า การใช้ dataclass ทำให้การสร้าง Constructor ง่ายขึ้นเยอะ เพราะเราไม่ต้องเขียนเอง ฮ่า ๆ

Singletons

และตัวสุดท้ายของวันนี้คือ Singletons ก่อนเราจะไปเข้าใจว่ามันทำอะไร เรามาเข้าใจ Concept ของ Singleton อย่างง่าย ๆ ก่อน ตามชื่อของมันเลยคือ Single ที่ไม่ได้แปลว่าโสด แต่แปลว่า หนึ่ง หรือก็คือ เป็น Object หรือตัวแปรอะไรบางอย่างที่เราจะกำหนดให้มันมีเพียงตัวเดียวเท่านั้น เช่น Pointer ในการอ่านไฟล์ หรือ Object บางอย่างที่เราต้องการ Sync ระหว่างส่วนต่าง ๆ ของโปรแกรมเรา Concept นี้ เราอาจจะไม่เคยได้ยินบน Python เท่าไหร่ แต่ถ้าเขียนพวก C/C++ มา อาจจะเคยได้ยินมาก่อน มันใช้เยอะมาก ๆ ช่วยชีวิตเรามาเยอะละ มาเขียน Python เราเลยอยากพา Concept นี้มาใช้ด้วย แต่นางไม่มี เราก็ต้องเขียนเองไป

def singleton(cls):
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

@singleton
class AnyClass:
    pass

ด้านในของ Singleton เราสร้าง instances เป็น Dict มาเพื่อเก็บ Object ที่เราสร้างเอาไป เพื่อให้ใน wrapper() ก่อนที่เราจะสร้าง Object เราจะเอาไปเช็คก่อนว่าใน instances มันมีอยู่แล้วมั้ย ถ้ามี ก็คืน Instance ที่เคยสร้างไว้ไป แต่ถ้าไม่มี มันก็จะสร้างอันใหม่ลงไป ในการใช้งาน เราก็แค่ยัด Decorator ลงไปบน Class ได้เลย

x = AnyClass()
y = AnyClass()

เรามาลองสร้าง Object จาก Class ที่เราใส่ Decorator Singleton เอาไว้ จาก Code ด้านบน ถ้าเราทำแบบนี้ปกติ เราน่าจะได้ Object 2 อันออกมาแต่เราใส่ Singleton มาแล้ว แปลว่า x และ y ควรจะเป็น Instance เดียวกัน เราลองมาเช็คกันดีกว่า

>>> x == y
True

เราลองเช็คดู เราจะเห็นได้เลยว่า x และ y เป็นตัวเดียวกันเลย ถ้าเราลอง Mutate x ตัว y ก็จะต้องเปลี่ยนตามด้วย เพราะมันคือตัวเดียวกัน นี่แหละคือความ Singleton อย่างง่ายใน Python

สรุป

3 Decorator ที่เราเอามาให้ดูกันในวันนี้จริง ๆ เป็นแค่ส่วนนึงของ Decorator ที่เราเขียนเอาไว้ใช้งานทั่ว ๆ ไป จริง ๆ มีอีกเป็นร้อยตัวเลย แต่เอาอันที่ใช้บ่อย ๆ ออกมาเล่า ถ้าใครอยากก๊อปไปใช้ก็ได้เลย มันไม่ได้เขียนยากอะไรมาก ถ้าใครมี Decorator เจ๋ง ๆ เอามาแชร์กันได้