2.23. Decorators

decorator คือไวยากรณ์ที่ห่อฟังก์ชัน (หรือเมธอด) ไว้ในฟังก์ชันอื่น wrapper สามารถเพิ่มพฤติกรรมก่อนและหลังการเรียกเดิม แทนที่ค่าที่คืนมา หรือแนบ metadata รูปแบบคือ:

@wrapper
def f():
    ...

บรรทัด @wrapper เทียบเท่ากับ f = wrapper(f) -- ฟังก์ชัน f ถูกสร้างตามปกติ จากนั้นส่งต่อให้ wrapper และผลลัพธ์ถูกผูกกลับคืนให้กับชื่อ f

A function entering a "wrapper" box on the left and emerging on the right as a decorated function.

decorator รับฟังก์ชันเข้าและคืนฟังก์ชันใหม่ออกมา

2.23.1. Method decorators ในตัว

Python มาพร้อม decorator บางตัวที่ใช้ภายในเนื้อหาของคลาส

2.23.1.1. @property

เปลี่ยนเมธอดให้กลายเป็น computed attribute ผู้เรียกเข้าถึงมันราวกับว่าเป็น attribute ธรรมดา (ไม่มีวงเล็บ) แต่เมธอดจะทำงานในแต่ละการอ่าน:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

ใช้เมื่อ attribute ที่ ดู เรียบง่ายต้องการการคำนวณเล็กน้อยอยู่เบื้องหลัง หากงานมีค่าใช้จ่ายสูง ควรใช้เมธอดธรรมดาแทน -- ผู้เรียกไม่คาดหวังว่าการอ่าน attribute จะช้า

2.23.1.2. @classmethod

กำหนดเมธอดที่รับ คลาส เป็นอาร์กิวเมนต์แรกแทนที่จะเป็น instance พารามิเตอร์แรกตามธรรมเนียมจะชื่อ cls:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

Class method คือวิธีมาตรฐานในการให้ alternative constructors -- ฟังก์ชัน factory ที่คืนค่า instance ที่สร้างในแบบที่ไม่ใช่ค่าเริ่มต้น

2.23.1.3. @staticmethod

กำหนดเมธอดที่ไม่รับทั้ง instance และคลาส -- มันเป็นเพียงฟังก์ชันธรรมดาที่อยู่ใน namespace ของคลาสเพื่อจุดประสงค์ด้านการจัดระเบียบ:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

ใช้อย่างประหยัด ถ้าฟังก์ชันไม่มีความเกี่ยวข้องกับสถานะของคลาสจริงๆ ฟังก์ชันระดับโมดูลธรรมดามักจะสะอาดกว่า

2.23.2. การเขียน decorator กำหนดเอง

decorator คือฟังก์ชันที่รับฟังก์ชันและคืนฟังก์ชัน รูปแบบขั้นต่ำ:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

ผลลัพธ์:

calling add

wrapper ปิดทับ func และส่งต่อทุกอย่างไปยังมัน *args / **kwargs ทำให้มันทำงานได้กับฟังก์ชันใดก็ตาม ไม่ใช่แค่ฟังก์ชันสองอาร์กิวเมนต์ รูปแบบนี้เป็นพื้นฐานของ decorator ที่ซับซ้อนมากขึ้น (การจับเวลา, การแคช, การลองใหม่เมื่อล้มเหลว) แต่แก่นกลางเสมอเหมือนกัน: รับฟังก์ชันเข้า คืนฟังก์ชันออก