
@singledispatch
@singledispatch
允許函數充當 dispatch generic 函數 (single dispatch 函數),意味著它根據其第一個參數的類型具有不同的行為。 在其他編程語言中,此功能有時也稱為 "overloading"。
一旦定義了 single dispatch 函數,來自其他 libraries 或 modules 的程式碼,就可以用新的情況擴充它。 這意味著它們可以新增方法來處理您在編寫原始函數時未處理或不知道的類型。
如果您正在編寫 libraries,您的使用者可以輕鬆擴充該功能,以處理您未預料到的類型或用例。 同樣,如果您正在處理具有許多貢獻者的大型程式碼 libraries,則程式碼的不同部分可以向函數新增功能,而無需修改函數的原始定義。 這可以使程式碼更容易維護,並隨著時間的推移而發展。
如果您的類型檢查需求很簡單,並且不同類型的行為不太複雜,那麼使用 if
語句或單獨的函數可能是更簡單、更直接。
Overloading 有助於組織代碼,提高可讀性,並使得將來更容易將函數的功能擴展到其他類型。
@singledispatch
幫助您組織取決於特定類型參數的函數的不同實現,而不是擁有一個具有許多 if/elif
分支的單一函數,或者許多名稱略有不同的不同函數。
Python 本身並不支持 overloading,但 @singledispatch
是實現類似結果的 Pythonic 方法。要向函數新增 overloading 實現,請使用通用函數的 .register
屬性,該屬性可以用作 decorator。 對於用type annotations (PEP 484) 的函數,decorator 將自動推斷第一個參數的類型,您也可以顯式地將類型參數傳遞給 .register()
屬性。
dispatch generic (single dispatch) 函數
dispatch generic (single dispatch) 函數是由多個方法組成的函數,即使它們以相同的方式調用,它也會根據單個參數的類型執行不同的操作。
這個概念也稱為函數 overloading,廣泛應用於許多物件導向的語言中。 Python 作為一種動態類型語言,不支持傳統意義上的函數 overloading。 但是,從 Python 3.4 開始,提供了 @singledispatch
來提供類似的功能。
範例 – Type annotations
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)"
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)"
範例 – @.register
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
範例 – @.register 跨 module
# 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"
@singledispatchmethod
它是 @singledispatch
的演變,設計用於實例方法 (Class 方法)。
@<method>.register
第一個參數是實例 (self) 或 Class (cls),第二個參數是考慮調度的參數。
@singledispatchmethod
支援與其他 decorators 嵌套。但 @singledispatchmethod
, @<method>.register
必須是最外層 decorators。
例如:@classmethod
, @staticmethod
, @abstractmethod
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))
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]
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.")
reduce()
functools.reduce(function, iterable[,initializer])
提供了一種以累積方式,將帶有兩個參數的函數 (binary function) 應用於 iterable 中的所有項目的方法。
如果提供了 initializer
,它將用作減少過程中的初始值。 如果不是,則使用 iterable 的第一個元素。
減少過程:將函數應用於 iterable 的前兩個元素,然後再次將函數應用於結果和下一個元素,依此類推,直到處理完所有元素。
reduce()
有時會使程式碼更難以理解。 它通常可以替換為 built-in 函數或簡單迴圈,這可以使您的程式碼更易於閱讀和 debugging。
reduce()
大致等效以下程式碼 (詳見:itertools.accumulate()):
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
範例:
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
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
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)
cmp_to_key()
functools.cmp_to_key(func)
主要是為了幫助將 Python 2 程式碼遷移到 Python 3 程式碼,Python 3 程式碼不支持 cmp
但支持 key
。對於大多數 Python 3 任務,您可以放心地忽略它。
參考資料
你應該要知道的 Python 實用模組 - functools 教學 - MyApollo
functools — Higher-order functions and operations on callable objects — Python 3.11.4 documentation
Last updated
Was this helpful?