> For the complete documentation index, see [llms.txt](https://docs.xiwind-corp.com/tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.xiwind-corp.com/tech/python-library/functools-han-shu-gao-ji-cao-zuo/ordering-wrapper-partial.md).

# ordering、wrapper、partial

## @total\_ordering

***

允許我們僅透過 2 個 Class comparison 方法，來自動完成一套完整的 comparison operations (`<`、`<=`、`==`、`!=`、`>=`、`>`) 方法。`@total_ordering` 允許您專注於 Class 如何比較的核心邏輯，並讓 Python 處理繁瑣的樣板。 (顯著簡化您的程式碼)

Class 必須使用 `__eq__` 和其他任一種方法 (`__lt__` (小於)、`__le__` (小於或等於)、`__gt__` (大於)、`__ge__` (大於或等於))，才能使用 `@total_ordering`。

{% hint style="info" %}
**`@total_ordering` 不會嘗試覆蓋 (實現) 已經存在的 comparison 方法。**

例如：繼承自 Class, SuperClasses or Abstract Class
{% endhint %}

{% hint style="info" %}
**大多數情況下，`@total_ordering` 增加的便利性，超過了它的性能開銷。**

如果性能基準測試表明這會造成給定應用程序的瓶頸，那麼實施所有 comparison 方法 (6 種) 將能輕鬆地提升速度。
{% endhint %}

{% code title="" %}

```python
from functools import total_ordering

@total_ordering
class MyClass():
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        if isinstance(other, MyClass):
            return self.value == other.value
        return NotImplemented

    def __lt__(self, other):
        if isinstance(other, MyClass):
            return self.value < other.value
        return NotImplemented
a = MyClass(10)
b = MyClass(20)

print(a == b)  # False
print(a != b)  # True
print(a < b)   # True
print(a > b)   # False
print(a <= b)  # True
print(a >= b)  # False

```

{% endcode %}

**comparison 方法：**

`self` 參數是對調用該方法的實例的引用，而 `other` 參數是對我們要比較的實例的引用。

假設我們有一個 `MyClass` Class 和兩個實例 `a` 和 `b`。當您在程式碼中調用 `a < b` 時，這實際上會被轉換為 `a.__lt__(b)`。

實例 `a` 是 `self`，實例 `b` 是 `other`，因此 Class 中必須有 `__lt__(self, other)` 此方法。

## @wraps()

***

`@wraps()`是一個 decorator factory，它將 `update_wrapper()` 應用於裝飾函數。 這通常用在裝飾器定義中，以確保保留被裝飾的原始函數的 metadata。

當我們創建一個 decorator 時，通常會用另一個函數替換或包裝(wrapper)一個函數。 因此原始函數的 metadata 會丟失 (name, docstring, module, parameter list...)。 如果我們需要訪問這些訊息，這就會出現問題 (例如：debugging 或檢查)。

這就是 `@wraps()` 派上用場的地方。 該 decorator 用於我們的 decorator 的定義中，以保存裝飾函數的 metadata。

{% hint style="success" %}
**定義 decorators 時使用 `@wraps()` 是一個很好的做法，以確保 debugging 按預期的工作。**
{% endhint %}

{% hint style="success" %}
**`@wraps()` 確保保留修飾函數 metadata。 這對於大型程式碼 library 中的日誌記錄和 debugging 非常有用。**
{% endhint %}

{% code title="PYTHON" %}

```python
import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("Before calling the function")
        result = func(*args, **kwargs)
        print("After calling the function")
        return result
    return wrapper

@my_decorator
def greet(name):
    """Greets a person"""
    print(f"Hello, {name}")

greet("Alice")
print(greet.__name__)  # Output: greet
print(greet.__doc__)   # Output: Greets a person
```

{% endcode %}

### 範例 – 帶參數的 Decorators

***

有時，我們想要定義帶有參數的 decorators。 這需要另一層 wrapping。

在此示例中，`repeat()`是一個 decorator factory，它接受參數`num_times`。 它返回 decorator `decorator_repeat()`，而 decorator 又返回包裝函數。 `@wraps(func)` 保留了原始函數的 metadata。

{% code title="PYTHON" %}

```python
import functools

def repeat(num_times):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                value = func(*args, **kwargs)
            return value
        return wrapper_repeat
    return decorator_repeat

@repeat(num_times=4)
def greet(name):
    """Greets a person"""
    print(f"Hello, {name}")

greet("Alice")
'''
Hello, Alice
Hello, Alice
Hello, Alice
Hello, Alice
'''
```

{% endcode %}

### 範例 – Debugging

***

{% code title="PYTHON" %}

```python
import functools
import time

# Define debugging decorator
def debug(func):
    """Prints the function signature and return value"""
    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        args_repr = [repr(a) for a in args]
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
        signature = ", ".join(args_repr + kwargs_repr)
        print(f"Calling {func.__name__}({signature})")
        start = time.time()
        value = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} returned {value!r} in {end-start:.5f} s")
        return value
    return wrapper_debug

@debug
def make_greeting(name, age=None):
    """Form a greeting message"""
    if age is None:
        return f"Howdy {name}!"
    else:
        return f"Whoa {name}! {age} already, you are growing up!"

make_greeting("Alice", age=12)
```

{% endcode %}

**執行結果:**&#x20;

```
Calling make_greeting('Alice', age=12)
make_greeting returned 'Whoa Alice! 12 already, you are growing up!' in 0.00000 s
```

## update\_wrapper()

***

`update_wrapper()` 函數和 `@functools.wraps()` 裝飾器都具有相似的目的：使包裝函數在屬性方面，模仿被包裝函數(例如：`__name__`、`__doc__`...)。

**區別在於它們的使用方式：**

* `functools.update_wrapper(wrapper, wrapped)`： 這是一個普通的函數，你可以明確地調用它，將包裝函數和被包裝的函數作為 arguments。它就地修改包裝器函數，並返回該函數。這個函數經常被用在 decorator 的定義中，以使 decorator 模仿被裝飾的函數。
* `@functools.wraps(wrapped)`： 這是一個 decorator，你在 decorator 內部定義包裝函數時使用。它簡化了`functools.update_wrapper()`的用法，允許你用 decorator 的語法將其包裝起來。

{% code title="PYTHON" %}

```python
update_wrapper(
	wrapper, 
	wrapped, 
	assigned=('__module__', '__name__', 
			'__qualname__', '__doc__', 
			'__annotations__'), 
	updated=('__dict__',))
```

{% endcode %}

`assigned` 和 `updated` 定義了原始函數的哪些屬性被轉移到包裝函數中。

* `assigned` 屬性被直接轉移。
* `updated` 屬性在包裝函數的 dictionary 中被更新。

`update_wrapper()` 向引用原始函數的包裝函數加入 `__wrapped__` 屬性。 這意味著如果您有一個包裝函數，您可以透過 `__wrapped__` 屬性訪問原始函數。此屬性對於 introspection 或繞過 decorators (例如：`@lru_cache`) 特別有用。

{% hint style="info" %}
**Introspection：這是指在運行時，檢查物件屬性的過程。例如：物件的類型、屬性、方法...。**
{% endhint %}

{% hint style="info" %}
**`update_wrapper()`可以用於任何可調用物件，不僅僅是函數。**&#x20;

如果原始物件缺少 `assigned` 或 `updated` 中列出的任何屬性，`update_wrapper()` 將忽略它們，並且不會嘗試在包裝函數中設置它們。

然而，如果包裝函數缺少 `updated` 中列出的任何屬性，這將引發一個`AttributeError`。
{% endhint %}

**範例:**

{% code title="PYTHON" %}

```python
def decorator(func):
    def wrapper(*args, **kwargs):
        # some decoration here
        pass
    functools.update_wrapper(wrapper, func)
    return wrapper
```

{% endcode %}

{% code title="PYTHON" %}

```python
def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # some decoration here
        pass
    return wrapper
```

{% endcode %}

### 包含的方法

***

在 `functools.update_wrapper()` 函數中，屬性 `__module__`、`__name__`、`__qualname__`、`__doc__`、`__annotations__` 直接從原始函數複製到包裝函數。 原始函數的 `__dict__` 屬性用於更新包裝函數的 `__dict__` 屬性。 這有助於使包裝函數看起來更像原始函數。

在 Python 中，每個物件都有許多 built-in 屬性，讓我們了解一下這些屬性：

* `__module__`：該屬性包含定義函數的 module 名稱。 如果函數定義在 main module 中，則 `__module__` 屬性為 `__main__`。
* `__name__`：該屬性包含函數的原始名稱。
* `__qualname__`：該屬性是 "qualified name" 的縮寫，包括函數名稱、任何封閉 Class 。 它有助於明確地識別函數或 Class。
* `__doc__`：該屬性包含函數的 docstring，它是為函數提供文檔的 string。
* `__annotations__`：該屬性是一個字典，包含函數參數或返回值的所有註釋。
* `__dict__`：該屬性是一個字典，包含函數的名稱空間，即其屬性名稱和值。

{% code title="PYTHON" %}

```python
def sample_function(a: 'annotation for a', b: 'annotation for b') -> 'annotation for return':
    """This is a sample function for demonstrating function attributes."""
    pass

print("Module:", sample_function.__module__)
print("Name:", sample_function.__name__)
print("Qualified Name:", sample_function.__qualname__)
print("Doc:", sample_function.__doc__)
print("Annotations:", sample_function.__annotations__)
print("Dict:", sample_function.__dict__)
```

{% endcode %}

**執行結果：**

```TXT
Module: __main__
Name: sample_function
Qualified Name: sample_function
Doc: This is a sample function for demonstrating function attributes.
Annotations: {'a': 'annotation for a', 'b': 'annotation for b', 'return': 'annotation for return'}
Dict: {}
```

### 範例 – 略過 (不調用) Decorator

***

假設您出於某種原因想要繞過 (不調用) decorator ， 您可以透過調用 `__wrapped__` 屬性來做到這一點(例如：`@lru_cache`)。該技術的實際用途，取決於您正在使用的特定 decorators 和函數。

**Bypassing the decorator:**

{% code title="PYTHON" %}

```python
import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Before calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"After calling {func.__name__}")
        return result
    return wrapper

@my_decorator
def greet(name):
    """Greets a person"""
    print(f"Hello, {name}!")

# Calling the decorated function (wrapper function)
greet("Alice")
'''
Output:
Before calling greet
Hello, Alice!
After calling greet
'''

# Calling the original function  (wrapped function)
greet.__wrapped__("Alice")  
# Output: Hello, Alice!

```

{% endcode %}

{% code title="PYTHON" %}

```python
import functools

@functools.lru_cache
def expensive_computation(x):
    print(f"Computing for {x}")
    return x * x

print(expensive_computation(4))  # Output: Computing for 4, 16
print(expensive_computation(4))  # Output: 16

print(expensive_computation.__wrapped__(4))  # Output: Computing for 4, 16
```

{% endcode %}

### 範例 – @wraps() vs update\_wrapper()

***

{% code title="PYTHON" %}

```python
import functools

def double_result_decorator(func):
    @functools.wraps(func)  # This is a shortcut for calling update_wrapper()
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result * 2
    return wrapper

@double_result_decorator
def add(a, b):
    """Adds two numbers."""
    return a + b

print(add(1, 2))    # Outputs: 6
print(add.__name__) # Outputs: 'add'
print(add.__doc__)  # Outputs: 'Adds two numbers.'
```

{% endcode %}

{% code title="PYTHON" %}

```python
import functools

def double_result_decorator(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result * 2
    functools.update_wrapper(wrapper, func)
    return wrapper

@double_result_decorator
def add(a, b):
    """Adds two numbers."""
    return a + b

print(add(1, 2))    # Outputs: 6
print(add.__name__) # Outputs: 'add'
print(add.__doc__)  # Outputs: 'Adds two numbers.'
```

{% endcode %}

<br>

## partial()

***

允許您建立一個帶有一些預設參數的新函數，我們只需要帶入剩下所需的參數即可。這在具有許多參數的函數或建立函數的變體，以供特定用途時非常有用。(例如：修復函數、隱藏函數參數建立 API)

```python
partial(func, /, *args, **keywords)
```

* `func`：建立其 partial 函數的原始函數。
* `args`：partial 函數的 positional arguments。
* `keywords`：partial 函數的 keyword arguments。

{% hint style="success" %}
**`partial()` 在更複雜的情況下使用時，特別有用。當您需要與函數本身一起傳遞某些 "狀態" 訊息或 context 時。**

尤其是當您處理需要提供回調函數，但又想保留一些函數的 libraries or frameworks 。
{% endhint %}

{% hint style="info" %}
**`partial()` 並不會實際運行自己本身。 在調用時，將使用預定參數運行原始函數。**
{% endhint %}

`partial()` 大致等效以下程式碼：

{% code title="PYTHON" %}

```python
def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc
```

{% endcode %}

### 範例 – 基礎

***

{% code title="PYTHON" %}

```python
import functools

def increment_by(val, increment):
    return val + increment
    
increment_by_5 = functools.partial(increment_by, increment=5)

numbers = [1, 2, 3, 4, 5]
result = list(map(increment_by_5, numbers))

print(result)  # Output: [6, 7, 8, 9, 10]
```

{% endcode %}

{% code title="PYTHON" %}

```python
import functools
from PIL import Image

def transform_image(image, angle, size):
    rotated_image = image.rotate(angle)
    resized_image = rotated_image.resize(size)
    return resized_image

specific_transform = functools.partial(transform_image, 
                                       angle=90, size=(500, 500))

img = Image.open("test.jpg")
new_img = specific_transform(img)
```

{% endcode %}

### 範例 – 帶參數的 Decorators

***

Decorators 透過將函數作為參數，並返回一個新函數來工作，該新函數通常會擴展或更改輸入函數的行為。 因此，Decorators 通常被設計為只接受一個 argument：**被裝飾的函數**。

當您想要建立一個接受自己的參數的 decorator 時 (除了它所裝飾的函數之外)，常見的做法是創建一個 Decorator factory (返回裝飾器的函數) 或是使用 `partial()` 做為 decorator factory。

**範例：使用 Decorator factory**

{% code title="PYTHON" %}

```python
import functools

# Create decorator factory
def repeat(num_times):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_times):
                value = func(*args, **kwargs)
            return value
        return wrapper_repeat
    return decorator_repeat

@repeat(num_times=4)
def greet(name):
    """Greets a person"""
    print(f"Hello, {name}")

greet("Alice")
'''
Hello, Alice
Hello, Alice
Hello, Alice
Hello, Alice
'''
```

{% endcode %}

**範例：使用 `partial()`**

{% code title="PYTHON" %}

```python
import functools

# Use partial() as decorator factory
def _decorator_repeat(func, num_times):
    @functools.wraps(func)
    def wrapper_repeat(*args, **kwargs):
        for _ in range(num_times):
            value = func(*args, **kwargs)
        return value
    return wrapper_repeat

def repeat(num_times):
    return functools.partial(_decorator_repeat, num_times=num_times)

@repeat(num_times=4)
def greet(name):
    """Greets a person"""
    print(f"Hello, {name}")

greet("Alice")
'''
Hello, Alice
Hello, Alice
Hello, Alice
Hello, Alice
'''
```

{% endcode %}

## partialmethod()

***

用於 "凍結" 方法的某些參數，生成固定指定參數的新可調用物件。 它可以被視為 `partial()` 的方法版本，只不過它被設計為用作定義，而不能直接可調用。

{% code title="PYTHON" %}

```python
functools.partialmethod(func, /, *args, **keywords)
```

{% endcode %}

* `func`：凍結某些參數的方法，即調用的方法。
* `*args`：傳遞給 `func` 的 positional arguments。
* `**keywords`：傳遞給 `func` 的 keyword arguments。

{% hint style="success" %}
**`partialmethod()` 允許您透過隱藏一些實現細節來創建更清晰、更使用者友好的 API，或是使程式碼更具可讀性。**
{% endhint %}

{% hint style="info" %}
**該函數返回一個新的 partial 方法描述符，當從實例訪問時，其行為類似於屬性。 partial 方法將提供的 `args`, `keywords` 綁定到調用的方法。**
{% endhint %}

{% hint style="info" %}
**`partialmethod()`不知道也不關心 decorators —— 它只是包裝您提供給它的函數。 這意味著您可以將 partialmethod 與 decorators 一起使用。**
{% endhint %}

**範例：**

{% code title="PYTHON" %}

```python
from functools import partialmethod

class Cell():
    def __init__(self):
        self._alive = False

    @property
    def alive(self):
        return self._alive

    def set_state(self, state):
        self._alive = bool(state)

    set_alive = partialmethod(set_state, True)
    set_dead = partialmethod(set_state, False)

c = Cell()

print(c.alive)        # Output: False
print(c.set_alive())  # Output: None
print(c.alive)        # Output: True

```

{% endcode %}

{% code title="PYTHON" %}

```python
from functools import partialmethod

class MyModel():
    def calculate(self, x, y, operation):
        if operation == 'add':
            return x + y
        elif operation == 'mul':
            return x * y
        else:
            raise ValueError("Invalid operation")

    add = partialmethod(calculate, operation='add')
    mul = partialmethod(calculate, operation='mul')

model = MyModel()
print(model.add(3, 5))  # 8
print(model.mul(3, 5))  # 15
```

{% endcode %}

{% code title="PYTHON" %}

```python
import cv2
import functools

class ImageProcessor():
    def __init__(self, image):
        self.image = image

    def filter_image(self, filter_type, *args, **kwargs):
        if filter_type == 'blur':
            return cv2.blur(self.image, *args, **kwargs)
        elif filter_type == 'GaussianBlur':
            return cv2.GaussianBlur(self.image, *args, **kwargs)
        else:
            raise ValueError("Unknown filter type")

    blur = functools.partialmethod(filter_image, 'blur')
    GaussianBlur = functools.partialmethod(filter_image, 'GaussianBlur')

image = cv2.imread("70720315_p2.jpg")
processor = ImageProcessor(image)

# Use partialmethod()
blurred_image = processor.blur((5,5))

# Not use partialmethod()
blurred_image = processor.filter_image('blur', (5,5))
```

{% endcode %}

\ <br>
