# Overloading

## @singledispatch

***

`@singledispatch` 允許函數充當 dispatch generic 函數 (single dispatch 函數)，意味著它根據其第一個參數的類型具有不同的行為。 在其他編程語言中，此功能有時也稱為 "overloading"。

一旦定義了 single dispatch 函數，來自其他 libraries 或 modules 的程式碼，就可以用新的情況擴充它。 這意味著它們可以新增方法來處理您在編寫原始函數時未處理或不知道的類型。

如果您正在編寫 libraries，您的使用者可以輕鬆擴充該功能，以處理您未預料到的類型或用例。 同樣，如果您正在處理具有許多貢獻者的大型程式碼 libraries，則程式碼的不同部分可以向函數新增功能，而無需修改函數的原始定義。 這可以使程式碼更容易維護，並隨著時間的推移而發展。

{% hint style="success" %}
如果您的類型檢查需求很簡單，並且不同類型的行為不太複雜，那麼使用 `if` 語句或單獨的函數可能是更簡單、更直接。
{% endhint %}

{% hint style="success" %}
**Overloading 有助於組織代碼，提高可讀性，並使得將來更容易將函數的功能擴展到其他類型。**&#x20;

`@singledispatch` 幫助您組織取決於特定類型參數的函數的不同實現，而不是擁有一個具有許多 `if/elif` 分支的單一函數，或者許多名稱略有不同的不同函數。
{% endhint %}

Python 本身並不支持 overloading，但 `@singledispatch` 是實現類似結果的 Pythonic 方法。要向函數新增 overloading 實現，請使用通用函數的 `.register` 屬性，該屬性可以用作 decorator。 對於用type annotations ([PEP 484](https://peps.python.org/pep-0484/)) 的函數，decorator 將自動推斷第一個參數的類型，您也可以顯式地將類型參數傳遞給 `.register()` 屬性。

{% hint style="info" %}
**`.register` 的簽名必須 base 函數的簽名符合。(即 `@<function>.register`)**
{% endhint %}

{% hint style="info" %}
為了能夠註冊 `lambda` 和預先存在的函數，`.register` 屬性也可以以函數形式使用。 `.register` 屬性返回未修飾的函數。

這使得 `@<function>.register` 能夠獨立地 stacking、 pickling、為每個 `@<function>.register` 建立 unit tests。
{% endhint %}

### dispatch generic (single dispatch) 函數

***

dispatch generic (single dispatch) 函數是由多個方法組成的函數，即使它們以相同的方式調用，它也會根據單個參數的類型執行不同的操作。

這個概念也稱為函數 overloading，廣泛應用於許多物件導向的語言中。 Python 作為一種動態類型語言，不支持傳統意義上的函數 overloading。 但是，從 Python 3.4 開始，提供了 `@singledispatch` 來提供類似的功能。

### 範例 – Type annotations

***

{% hint style="info" %}
**`_` 在 Python 中用作一次性變數的約定。(函數是匿名的)**
{% endhint %}

{% hint style="info" %}
**這裡的 verbose 參數只是一個標誌，允許開發人員或使用者控制函數應輸出多少訊息，這對於 debugging 或理解函數正在做什麼非常有幫助。**

在許多 programming contexts 中很常見。尤其是在不同 contexts 中，使用的函數或 scripts。
{% endhint %}

{% code title="PYTHON" %}

```python
from functools import singledispatch

@singledispatch
def func(obj, verbose=False):
    if verbose:
        print("function called:", type(obj))
    return str(obj)

@func.register(int)
def _(obj, verbose=False):
    if verbose:
        print("function called:", type(obj))
    return float(obj)

@func.register(set)
def _(obj, verbose=False):
    if verbose:
        print("function called:", type(obj))
    return list(obj)

# Usage
print(func(100))             # Outputs: 100.0
print(func({1, 2, 3, 4}))    # Outputs: [1, 2, 3, 4]
print(func([1, 2]))          # Outputs: "[1, 2]"
print(func((1, 2)))          # Outputs: "(1, 2)"
```

{% endcode %}

{% code title="PYTHON" %}

```python
from functools import singledispatch

@singledispatch
def func(obj, verbose=False):
    if verbose:
        print("function called:", type(obj))
    return str(obj)

@func.register
def _(obj:int, verbose=False):
    if verbose:
        print("function called:", type(obj))
    return float(obj)

@func.register
def _(obj:set, verbose=False):
    if verbose:
        print("function called:", type(obj))
    return list(obj)

# Usage
print(func(100))             # Outputs: 100.0
print(func({1, 2, 3, 4}))    # Outputs: [1, 2, 3, 4]
print(func([1, 2]))          # Outputs: "[1, 2]"
print(func((1, 2)))          # Outputs: "(1, 2)"
```

{% endcode %}

### 範例 – @.register

***

{% code title="PYTHON" %}

```python
from functools import singledispatch

# Define base function for singledispatch
@singledispatch
def process(data):
    print(f"Base function received data of type {type(data).__name__}")

# =========================================================
# Register function
@process.register
def _(data: str):
    print(f"Received value '{data}'")
    
# =========================================================
# Register function (stacking)
@process.register(int)
@process.register(float)
def _(data):
    print(f"Received value {data}")
    
# =========================================================
# Register function (lambda)
process.register(list, lambda x: print(f"Received values {x}"))

# =========================================================
# Register function (Pre-existing)
def process_bool(data):
    print(f"Received with value {data}")

# Registering pre-existing function
process.register(bool, process_bool)

# =========================================================
# testing
process(10)             # Output: Received value 10
process(10.0)           # Output: 10.0
process("Hello World")  # Output: 'Hello World'
process([1, 2, 3])      # Output: [1, 2, 3]
process(True)           # Output: True
```

{% endcode %}

<br>

### 範例 – @.register 跨 module

***

{% code title="PYTHON" %}

```python
# In module1.py
from functools import singledispatch

@singledispatch
def process(data):
    print(f"Don't know how to process {data}")

@process.register(int)
def _(data):
    print(f"Processing integer: {data}")

# =========================================================
# In module2.py
from module1 import process

@process.register(str)
def _(data):
    print(f"Processing string: {data}")

process(10)    # Outputs: "Processing integer: 10"
process("hi")  # Outputs: "Processing string: hi"

```

{% endcode %}

## @singledispatchmethod

***

它是 `@singledispatch` 的演變，設計用於實例方法 (Class 方法)。

`@<method>.register` 第一個參數是實例 (self) 或 Class (cls)，第二個參數是考慮調度的參數。

{% hint style="danger" %}
**`@singledispatchmethod` 支援與其他 decorators 嵌套。但 `@singledispatchmethod`, `@<method>.register` 必須是最外層 decorators。**&#x20;

例如：`@classmethod`, `@staticmethod`, `@abstractmethod`
{% endhint %}

{% code title="PYTHON" %}

```python
from functools import singledispatchmethod

class Negator():
    @singledispatchmethod
    @classmethod
    def neg(cls, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    @classmethod
    def _(cls, arg: int):
        return -arg

    @neg.register
    @classmethod
    def _(cls, arg: bool):
        return not arg

print(Negator.neg(16))
print(Negator.neg(True))
```

{% endcode %}

{% code title="PYTHON" %}

```python
import functools
 
class MultiProcessor():
    @functools.singledispatchmethod
    def process(self, data):
        raise NotImplementedError("Unsupported type")
 
    @process.register
    def _(self, data: int):
        print(f"Processing an integer: {data}")
        # Do something specific to int
 
    @process.register
    def _(self, data: str):
        print(f"Processing a string: {data}")
        # Do something specific to str
 
    @process.register
    def _(self, data: list):
        print(f"Processing a list: {data}")
        # Do something specific to list
 
# Usage
processor = MultiProcessor()
processor.process(42)              # Output: Processing an integer: 42
processor.process("Hello world")   # Output: Processing a string: Hello world
processor.process([1, 2, 3])       # Output: Processing a list: [1, 2, 3]
```

{% endcode %}

{% code title="PYTHON" %}

```python
import cv2
import numpy as np
from functools import singledispatchmethod

class ImageProcessor():
    @singledispatchmethod
    def process(self, image):
        print("Not implemented for this type.")

    @process.register
    def _(self, image: cv2.VideoCapture):
        # Processing for video captured from a camera
        print("Processing video capture.")

    @process.register
    def _(self, image: np.ndarray):
        # Processing for an image loaded into a NumPy array
        print("Processing numpy array.")
```

{% endcode %}

## reduce()

***

`functools.reduce(function, iterable[,initializer])` 提供了一種以累積方式，將帶有兩個參數的函數 (binary function) 應用於 iterable 中的所有項目的方法。

如果提供了 `initializer`，它將用作減少過程中的初始值。 如果不是，則使用 iterable 的第一個元素。

**減少過程**：將函數應用於 iterable 的前兩個元素，然後再次將函數應用於結果和下一個元素，依此類推，直到處理完所有元素。

{% hint style="danger" %}
**`reduce()` 有時會使程式碼更難以理解。 它通常可以替換為 built-in 函數或簡單迴圈，這可以使您的程式碼更易於閱讀和 debugging。**
{% endhint %}

{% hint style="info" %}
**`reduce()` 始終返回單個資料，該值可以是任何數據類型。 返回的資料類型由您提供給 `reduce()` 的函數決定。**
{% endhint %}

`reduce()` 大致等效以下程式碼 (詳見：[itertools.accumulate()](https://docs.python.org/3/library/itertools.html#itertools.accumulate))：

{% code title="PYTHON" %}

```python
def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value
```

{% endcode %}

**範例：**

{% code title="PYTHON" %}

```python
import functools

def  bitwise_or_operation(a, b):
    return a | b

data = [1, 2, 4, 8, 16, 32]
result = data[0]

# Simple loop
for i in range(1, len(data)):
    result = bitwise_or_operation(result, data[i])
print(result)  # Output: 63

# reduce()
result = functools.reduce(bitwise_or_operation, data)
print(result)  # Output: 63

# reduce() + lambda
result = functools.reduce(lambda a, b: a | b, data)
print(result)  # Output: 63
```

{% endcode %}

{% code title="PYTHON" %}

```python
import functools

def multiply(x, y):
    return x * y

numbers = [1, 2, 3, 4, 5]
product = 10

# Simple loop
for num in numbers:
    product = multiply(product, num)
print(product)  # Output: 1200

# reduce()
product = functools.reduce(multiply, numbers, 10)
print(product)  # Output: 1200

# reduce() + lambda
product = functools.reduce(lambda x, y: x * y, numbers, 10)
print(product)  # Output: 1200
```

{% endcode %}

{% code title="PYTHON" %}

```python
import cv2
import functools
from pathlib import Path

# You might need to resize or crop them depending on your use case
def blend_images(image1, image2, alpha=0.5):
    return cv2.addWeighted(image1, alpha, image2, 1 - alpha, 0)

file_dir = r".\001"
image_files = Path(file_dir).glob("*.png")
images = [cv2.imread(str(image)) for image in image_files]

blended_image = functools.reduce(blend_images, images)
cv2.imwrite('output.jpg', blended_image)
```

{% endcode %}

## cmp\_to\_key()

***

`functools.cmp_to_key(func)` 主要是為了幫助將 Python 2 程式碼遷移到 Python 3 程式碼，Python 3 程式碼不支持 `cmp` 但支持 `key`。對於大多數 Python 3 任務，您可以放心地忽略它。

## 參考資料

***

[你應該要知道的 Python 實用模組 - functools 教學 - MyApollo](https://myapollo.com.tw/blog/python-functools/)

[functools — Higher-order functions and operations on callable objects — Python 3.11.4 documentation](https://docs.python.org/3/library/functools.html#functools.cache)


---

# 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/overloading.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.
