# 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>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.xiwind-corp.com/tech/python-library/functools-han-shu-gao-ji-cao-zuo/ordering-wrapper-partial.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
