Python類別中如何定義多個建構器方法重載和泛方法
什麼是「泛方法」?
由多個方法組成的方法,這些方法為不同的類型實現相同的操作。
舉個栗子
現在有個需求,需要你透過以下幾種方式建立一個自訂的日期類別(CustomDate
):
#時間戳記
年、月、日(包含三個整數的元組)
ISO 格式的字符字串
Datetime
類別
#一般實作
from datetime import date, datetime class CustomDate: def __init__(self, arg): if isinstance(arg, (int, float)): self.__date = date.fromtimestamp(arg) elif isinstance(arg, tuple) and len(arg) == 3 and all(map(lambda x: isinstance(x, int), arg): self.__date = date(*arg) elif isinstance(arg, str): self.__date = date.fromisoformat(arg) elif isinstance(arg, datetime): self.__date = datetime.date() else: raise TypeError("could not create instance from " + type(arg).__name__) @property def date(): return self.__date
註:這裡暫不討論傳入的日期/時間戳記合不合法,僅對類型做大致判斷。
有沒有更好的方式?
我們可以將不同的建置方式拆分為多個方法,並利用functools
中的singledispatchmethod
裝飾器來根據傳入的參數類型決定呼叫哪一個方法。
from datetime import date, datetime from functools import singledispatchmethod class CustomDate: @singledispatchmethod def __init__(self, arg): raise TypeError("could not create instance from " + type(arg).__name__) @__init__.register(int) @__init__.register(float) def __from_timestamp(self, arg): self.__date = date.fromtimestamp(arg) @__init__.register(tuple) def __from_tuple(self, arg): if len(arg) == 3 and all(map(lambda x: isinstance(x, int), arg)): self.__date = date(*arg) else: raise ValueError("could not create instance from a malformed tuple") @__init__.register(str) def __from_isoformat(self, arg): self.__date = date.fromisoformat(arg) @__init__.register(datetime) def __from_datetime(self, arg): self.__date = arg.date() @property def date(self): return self.__date
這樣一來,我們便能將每種參數類型的初始化獨立成一個個的方法了。
缺點
#1 單分派
在呼叫期間應該使用哪個方法實作由分派演算法決定。如果演算法只基於單一參數的類型來決定使用哪個方法實現,則稱其為單分派。
singledispatchmethod
就是就是單分派的。也就是說,只有第一個參數會作為考量。這在實際業務中是遠遠不足的。
#2 不支援 typing
然而,如上,對元組中元素類型判斷還是需要我們用 if
/else
實作。也就是說,我們不能使用 typing.Tuple[int, int, int]
。
作為一種折中的方案,或許我們可以定義一個 ThreeIntTuple
類別來對其進行限定,將這些判斷從 CustomDate
類別中隔離開來。
這裡只提供一個想法讓大家參考,我就不實現了(因為我們有更好的方式 xD)。
替代方案:multimethod 函式庫
這個函式庫不是標準函式庫之一,需要透過pip 安裝:
pip install multimethod
優勢
multimethod
採用的是多分派演算法,能更好地滿足更複雜的場景。此外,該程式庫對 typing
中的類型也有不錯的支援。
更更好的實踐方式
回到上面的問題,我們可以這麼改進:
使用
multimethod
方法來替代singledispatchmethod
;使用
Tuple[int, int, int]
來取代tuple
,不再需要手動校驗元組的長度和元素類型了;
from datetime import date, datetime from typing import Tuple, Union from multimethod import multimethod class CustomDate: @multimethod def __init__(self, arg): raise TypeError("could not create instance from " + type(arg).__name__) @__init__.register def __from_timestamp(self, arg: Union[int, float]): self.__date = date.fromtimestamp(arg) @__init__.register def __from_tuple(self, arg: Tuple[int, int, int]): self.__date = date(*arg) @__init__.register def __from_isoformat(self, arg: str): self.__date = date.fromisoformat(arg) @__init__.register def __from_datetime(self, arg: datetime): self.__date = arg.date() @property def date(self): return self.__date
究極好的實踐方式(真正的方法重載)
在此之前,先問大家一個簡單的問題(這跟我們之後的內容有很大的連結):
class A: def a(self): print(1) def a(self): print(2) A().a()
以上這段程式碼會輸出什麼?還是會拋出錯誤?
輸出 2
。
在 Python 中,如果定義了重名的方法,最後一個方法是會覆寫之前的方法的。
但你或許不知,我們可以透過元類別(metaclass)來改變這個行為:
class MetaA(type): class __prepare__(dict): def __init__(*args): pass def __setitem__(self, key, value): if self.get('a'): # Line 7 super().__setitem__('b', value) # Line 8 else: super().__setitem__(key, value) class A(metaclass=MetaA): def a(self): print(1) def a(self): print(2) A().a() # => 1 A().b() # => 2 # Line 22
在第7 和第8 行,我們將重名的a
方法改名為b
,並在第22 行成功地呼叫它了。
multimethod
的維護者們很好地運用了這一點,對重名的方法進行了處理,以達到一種「特殊的效果」。
回到正題,我們可以做出以下改進:
將
multimethod.multidata
設定為CustomDate
類別的元類別;將所有方法命名為
__init__
。
from datetime import date, datetime from typing import Tuple, Union from multimethod import multimeta class CustomDate(metaclass=multimeta): def __init__(self, arg: Union[int, float]): self.__date = date.fromtimestamp(arg) def __init__(self, arg: Tuple[int, int, int]): self.__date = date(*arg) def __init__(self, arg: str): self.__date = date.fromisoformat(arg) def __init__(self, arg: datetime): self.__date = arg.date() def __init__(self, arg): raise TypeError("could not create instance from " + type(arg).__name__) @property def date(self): return self.__date
從效果來看,這完全和靜態語言的方法重載一模一樣!
以上是Python類別中如何定義多個建構器方法重載和泛方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

在 Sublime Text 中運行 Python 代碼,需先安裝 Python 插件,再創建 .py 文件並編寫代碼,最後按 Ctrl B 運行代碼,輸出會在控制台中顯示。

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

在 Visual Studio Code(VSCode)中編寫代碼簡單易行,只需安裝 VSCode、創建項目、選擇語言、創建文件、編寫代碼、保存並運行即可。 VSCode 的優點包括跨平台、免費開源、強大功能、擴展豐富,以及輕量快速。

在 Notepad 中運行 Python 代碼需要安裝 Python 可執行文件和 NppExec 插件。安裝 Python 並為其添加 PATH 後,在 NppExec 插件中配置命令為“python”、參數為“{CURRENT_DIRECTORY}{FILE_NAME}”,即可在 Notepad 中通過快捷鍵“F6”運行 Python 代碼。
