iT邦幫忙

1

[工作上的筆記] python 裝飾器、屬性裝飾器、類裝飾器

  • 分享至 

  • xImage
  •  

前言

因為想要了解效能瓶頸,得要量測每個程式花了多少時間,因此原本以為得要在每個程式裡面都要用這樣的方式來量測程式執行時間:

t1 = time.time()
...程式主體...
t2 = time.time()
print(t2-t1)

但每個程式都要寫實在是太麻煩了,後來發現了修飾器真的是好東西,筆記一下用法

python 常見修飾器

1. 修飾器(decorator)

用來修改函數的修飾子,通常這個修飾器是個函數,接收另一個函數作為參數,並返回一個新的函數,這個新函數通常會在元函數執行前後做一些額外工作像是紀錄日誌、或者是函數執行時間:

def time_it(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start} seconds to run.")
        return result
    return wrapper

@time_it
def my_function():
    import time
    time.sleep(1)

my_function()

上述這樣的語法等效於:

my_function = time_it(my_function)

2. 類修飾子(class decorator):

是一種用來修改類行為的修飾子。類修飾子通常是一個函數,用來接收一個類作為參數,並返回一個新的類。這個新類通常會在原類的基礎上添加一些新的方法或屬性,或者修改原類中的某些方法或屬性,乍聽之下有點像是繼承,不過又彈性許多。

def add_methods(cls):
    def say_hello(self):
        print(f"Hello, {self.name}!")
    cls.say_hello = say_hello
    return cls

@add_methods
class MyClass:
    def __init__(self, name):
        self.name = name

obj = MyClass("Alice")
obj.say_hello()

上述這樣的語法等效於:

MyClass = add_methods(MyClass)

3. 屬性修飾子(attribute decorator):

是一種用來修改類屬性行為的修飾子。Python 中有兩種內置的屬性修飾子:@property 和 @classmethod。@property 修飾器用來定義一個計算屬性,它會在調用屬性時執行一些額外的操作;@classmethod 修飾器用來定義一個類方法,它會在調用類方法時執行一些額外的操作。

以下用python原有的屬性修飾子來做範例,包含@property, @setter, @classmethod:

class MyClass:
    def __init__(self, x):
        self._x = x

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        if value < 0:
            raise ValueError("x must be non-negative")
        self._x = value

    @classmethod
    def create(cls, x):
        return cls(abs(x))

obj = MyClass(10)
print(obj.x)    # Output: 10
obj.x = -5      # Raises ValueError: x must be non-negative
obj.x = 20
print(obj.x)    # Output: 20
obj2 = MyClass.create(-10)
print(obj2.x)   # Output: 10

@property

可以透過obj.x來返回物件的x的屬性,看起來似乎沒啥用,因為本來就可以用obj.x取得x屬性,但實際上,@property修飾子,可以想成是計算屬性,例如:

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
    
    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"

他可以把一個函數的輸出,當作是一種屬性來看待,讓物件的屬性能更彈性的組合,不用再額外call function才能獲得經計算後的屬性。

@setter

類似@property修飾子,但他是對物件的屬性設值時進行計算,例如阻擋設定不合規定的數值,或者是經計算之後再把數值寫入物件的屬性。

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
    
    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"
    
    @full_name.setter
    def full_name(self, value):
        parts = value.split(" ")
        if len(parts) != 2:
            raise ValueError("full_name must consist of exactly two parts")
        self.first_name, self.last_name = parts

我們使用 @full_name.setter 修飾器定義了一個設置器,這個設置器會在調用 person.full_name = value 時將 value 字串轉換為 first_name 和 last_name 的兩個部分。

@classmethod

修飾器可以用於定義類方法,類方法是一個與類本身相關聯的方法,而不是與類的實例相關聯的方法。

在上述的範例裡面,@classmethod可以確保建構物件的輸入一定大於0,以下介紹一些進階應用:
1. 創建替代建構函數

class Person:
    def __init__(self, name):
        self.name = name

    @classmethod
    def from_dict(cls, data):
        return cls(data['name'])

person_data = {'name': 'Alice'}
person = Person.from_dict(person_data)

定義了一個 Person 類,這個類有一個成員屬性 name。使用 @classmethod 修飾器定義了一個 from_dict 方法,這個方法可以從一個字典創建一個 Person 對象。在這個方法中,使用 cls 參數來引用類本身,並使用字典中的 name 鍵來創建新的 Person 對象。

2. 實現單例模式

class Singleton:
    _instance = None

    def __init__(self):
        if Singleton._instance is not None:
            raise ValueError("Singleton already instantiated")
        Singleton._instance = self

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls()
        return cls._instance

定義了一個 Singleton 類,這個類只能創建一個實例。使用 @classmethod 修飾器定義了一個 get_instance 方法,這個方法會檢查 _instance 屬性是否為 None,如果是的話就創建一個新的 Singleton 對象,並將其設置為 _instance 屬性。如果 _instance 屬性已經有值了,那麼這個方法就直接返回 _instance 屬性的值。

3. 實現工廠模式

class Shape:
    def draw(self):
        raise NotImplementedError

class Rectangle(Shape):
    def draw(self):
        print("Drawing rectangle")

class Circle(Shape):
    def draw(self):
        print("Drawing circle")

class ShapeFactory:
    @classmethod
    def create(cls, shape_type):
        if shape_type == 'rectangle':
            return Rectangle()
        elif shape_type == 'circle':
            return Circle()
        else:
            raise ValueError(f"Unknown shape type: {shape_type}")

使用 @classmethod 修飾器定義了一個 create 方法,這個方法可以根據不同的參數創建不同的形狀對象。在這個方法中,我們使用 shape_type 參數來決定要創建哪種形狀對象,然後返回對應的形狀對象。如果 shape_type 參數不是已知的形狀類型,那麼我們就引發一個 ValueError 異常。


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言