Python 函數(shù)如何實現(xiàn)“重載”

在這里插入圖片描述

文章地址:Python 函數(shù)實現(xiàn)重載

單分派泛函數(shù)

假如你想在交互模式下打印出美觀的對象顾犹,那么標準庫中的 pprint.pprint() 函數(shù)或許是一個不錯的選擇郁季。但是,如果你想 DIY 一個自己看著舒服的打印模式,那么你很可能會寫一長串的 if/else 語句虑稼,來判斷傳進來對象的類型。

def fprint(obj):
    if isinstance(obj, list):
        print_list(obj)
    elif isinstance(obj, tuple):
        print_tuple(obj)
    elif isinstance(obj, str):
        print_str(obj)

這樣做固然沒有錯势木,但是太多的 if 語句使得代碼不易擴展蛛倦,而且代碼可讀性也要大打折扣。

他山之石

首先讓我們先來看看其他語言會怎樣處理這樣的問題:

Java 支持方法重載啦桌,我們可以編寫同名方法溯壶,但是這些方法的參數(shù)要不一樣及皂,主要體現(xiàn)在參數(shù)個數(shù)與參數(shù)類型方面。下面我們重載了 fprint() 這個靜態(tài)方法且改,調(diào)用 fprint() 方法時验烧,如果傳進來的參數(shù)是字符串,那么就調(diào)用第一個方法又跛;如果傳進來的參數(shù)是整型碍拆,那么就調(diào)用第二個方法。

public class Overriding {
    // 方法一
    public static void fprint(String Astring){
        System.out.println("我是一個字符串");
        System.out.println(Astring);
    }
    // 方法二
    public static void fprint(int Aint){
        System.out.println("我是一個整型");
        System.out.println(Aint);
    }
    public static void main(String[] args){
        fprint("Hello, Python.");
        fprint(666);
    }
}

輸出結(jié)果:

我是一個字符串
Hello, Python.
我是一個整型
666

Python 的解決方案

Python 通過單分派泛函數(shù)部分支持了方法重載慨蓝。

官方文檔是這樣定義泛函數(shù)以及單分派函數(shù)的:

A generic function is composed of multiple functions implementing the same operation for different types. Which implementation should be used during a call is determined by the dispatch algorithm. When the implementation is chosen based on the type of a single argument, this is known as single dispatch.

也就是說單分派泛函數(shù)(single dispatch)可以根據(jù)第一個參數(shù)的類型感混,來判斷執(zhí)行哪一個函數(shù)體。

那么我們使用 singledispatch 重寫上面的例子:

  1. 首先我們要從functools 中導入 singledispatch
from functools import singledispatch
  1. singledispatch 是作為裝飾器使用的函數(shù)礼烈。裝飾器是 Python 中的語法糖弧满,@singledispatch 實際上相當于 singledispatch(fprint),這里我們并不關(guān)心 singledispatch 的內(nèi)部實現(xiàn)此熬,我們只需知道 singledispatch 可以實現(xiàn)分派機制就行庭呜。NotImplemented 是 Python 中的內(nèi)置常量,提醒我們沒有實現(xiàn)某個功能摹迷。注意這與 NotImplementedError 有天壤之別疟赊,后者會導致異常出現(xiàn),終止程序峡碉。當調(diào)用 fprint() 函數(shù)時近哟,如果參數(shù)的類型沒有被注冊,那么默認會執(zhí)行使用 @singledispatch 裝飾的函數(shù)鲫寄。

    @singledispatch
    def fprint(obj):
        return NotImplemented
    
  2. 我們使用 @<主函數(shù)名>.register(type) 來裝飾專門函數(shù)吉执。要注意分派函數(shù)可以有任意多個參數(shù),但是調(diào)用函數(shù)時執(zhí)行哪一部分功能只由函數(shù)第一個參數(shù)決定地来,也就是由 register 中聲明的參數(shù)類型決定戳玫。 而對于專門函數(shù)來說,函數(shù)名是無關(guān)緊要的未斑,使用 _ 更加簡潔明了咕宿。

@singledispatch
def fprint(obj):
    return NotImplemented


@fprint.register(str)
def _(obj):
    print('我是一個字符串')
    print(obj)
    

@fprint.register(int)
def _(obj):
    print('我是一個整型')
    print(obj)    
  1. Python 3.7 中新增了一個功能:即使用 type annotions 來注明第一個參數(shù)的類型。打印結(jié)果蜡秽,與使用裝飾器參數(shù)得到的結(jié)果相同府阀。
@fprint.register
def _(obj:str):
    print('我是一個字符串')
    print(obj)

最后我們對代碼進行測試,結(jié)果符合我們的預期:

>>> fprint('Nice to meet you, Java')
我是一個字符串
Nice to meet you, Java
>>> fprint(999)
我是一個整型
999
>>> fprint((12, 4))
NotImplemented

更復雜的例子

上面就是 single dispatch 的基本用法芽突,下面讓我們看一個稍微復雜點的例子试浙。

想象,你需要一個自定義的打印函數(shù)寞蚌,又不想過多地使用 if/else 分支田巴,那么你可以應用剛學到的 single dispatch 來解決問題钠糊。

  1. 首先要導入我們需要的庫,這里我們用到了幾個抽象基類壹哺,整數(shù) Integral 和 可變序列 MutableSequence抄伍。使用抽象基類,可以使得我們的程序可拓展性更強斗躏。使用 Integral 注冊的函數(shù)不僅支持常規(guī)的 int 類型逝慧,還支持Integral 的子類或者注冊為 Integral 的虛擬子類,甚至可以支持實現(xiàn)了 Integral “協(xié)議” 的類型啄糙。這充分體現(xiàn)了Python “鴨子類型” 的強大之處笛臣。可變序列也是如此隧饼,不僅支持常規(guī)的 list 類型沈堡,還支持符合要求的自定義類型。
from functools import singledispatch
from numbers import Integral
from collections.abc import MutableSequence
  1. 其次燕雁,我們定義默認行為诞丽。即沒有被注冊的類型,會執(zhí)行下面的函數(shù)拐格。這里僧免,為了方便起見,我們直接打印對象的類型與內(nèi)容捏浊。
@singledispatch
def pprint(obj):
    print(f'({obj.__class__.__name__}) {obj}')
>>> pprint('微信公眾號:Python高效編程')
(str) 微信公眾號:Python高效編程
  1. 第一個函數(shù)使用了 type annotations懂衩,注冊為 Integral 類型。第二個函數(shù)注冊為 float 類型金踪,打印的時候小數(shù)點后保留兩位浊洞。
@pprint.register
def _(obj:Integral):
    print(f'({obj.__class__.__name__}) {obj}')
 
    
@pprint.register(float)
def _(obj):
    print(f'({obj.__class__.__name__}) {obj:.2f}')
>>> pprint(666)
(int) 666
>>> pprint(66.6457)
(float) 66.65
  1. 我們還可以通過堆積(類型注冊)裝飾器,來實現(xiàn)對多種類型的支持胡岔。下面這個函數(shù)就支持三種類型法希,分別是元組,集合靶瘸,可變序列苫亦。
@pprint.register(tuple)
@pprint.register(set)
@pprint.register(MutableSequence)
def _(obj):
    print(f'{"-"*7}{obj.__class__.__name__}{"-"*8}')
    print(f'index   type      value')
    for index, value in enumerate(obj):
        print(f'{index:^6}->{type(value).__name__:<8}: {value}')
>>> a = [[1, 3, 4], 'name', 5, 6]
>>> pprint(a)
-------list--------
index   type      value
  0   ->list    : [1, 3, 4]
  1   ->str     : name
  2   ->int     : 5
  3   ->int     : 6
>>> b = {1, 3, 4}
>>> pprint(b)
-------set--------
index   type      value
  0   ->int     : 1
  1   ->int     : 3
  2   ->int     : 4
  1. 最后我們支持了字典類型:
@pprint.register(dict)
def _(obj):
    print(f'{"-"*7}{obj.__class__.__name__}{"-"*8}')
    print('     key            value')
    for k, v in sorted(obj.items()):
        print(f'({type(k).__name__}){k:<6} -> ({type(v).__name__}){v:<6}')
>>> a = {'part1': "Python高效編程",'part2':'關(guān)注轉(zhuǎn)發(fā)', 'part3': 666}
>>> pprint(a)
-------dict--------
     key            value
(str)part1  -> (str)Python高效編程
(str)part2  -> (str)關(guān)注轉(zhuǎn)發(fā)  
(str)part3  -> (int)666  

講完上面的例子,我們再補充一個小用法:

  • 假如我們想知道 pprint() 函數(shù)支持哪些類型怨咪,我們該怎么做呢屋剑?

pprint.registry 返回類型與函數(shù)地址的鍵值對,調(diào)用 keys() 方法獲取 pprint() 函數(shù)支持的類型惊暴。

>>> pprint.registry.keys()
dict_keys([<class 'object'>,
           <class 'numbers.Integral'>,
           <class 'float'>, 
           <class 'collections.abc.MutableSequence'>,
           <class 'set'>, <class 'tuple'>, <class 'dict'>])

總結(jié)

這篇文章饼丘,我們從一個簡單例子切入趁桃,介紹了 singledispatch 的簡單用法與使用案例辽话。如果你發(fā)現(xiàn)你的代碼需要使用分派函數(shù)肄鸽,不妨嘗試這種代碼風格。如果想深入了解 singledispatch 的用法油啤,不妨去 PEP 443functools docs 一探究竟典徘。

在這里插入圖片描述

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市益咬,隨后出現(xiàn)的幾起案子逮诲,更是在濱河造成了極大的恐慌,老刑警劉巖幽告,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梅鹦,死亡現(xiàn)場離奇詭異,居然都是意外死亡冗锁,警方通過查閱死者的電腦和手機齐唆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冻河,“玉大人箍邮,你說我怎么就攤上這事∵缎穑” “怎么了锭弊?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長擂错。 經(jīng)常有香客問我味滞,道長,這世上最難降的妖魔是什么马昙? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任桃犬,我火速辦了婚禮,結(jié)果婚禮上行楞,老公的妹妹穿的比我還像新娘攒暇。我一直安慰自己,他們只是感情好子房,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布形用。 她就那樣靜靜地躺著,像睡著了一般证杭。 火紅的嫁衣襯著肌膚如雪田度。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天解愤,我揣著相機與錄音镇饺,去河邊找鬼。 笑死送讲,一個胖子當著我的面吹牛奸笤,可吹牛的內(nèi)容都是我干的惋啃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼监右,長吁一口氣:“原來是場噩夢啊……” “哼边灭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起健盒,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绒瘦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后扣癣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惰帽,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年父虑,在試婚紗的時候發(fā)現(xiàn)自己被綠了善茎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡频轿,死狀恐怖垂涯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情航邢,我是刑警寧澤耕赘,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站膳殷,受9級特大地震影響操骡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赚窃,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一册招、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勒极,春花似錦是掰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至匾七,卻和暖如春絮短,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昨忆。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工丁频, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓席里,卻偏偏與公主長得像夺颤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子胁勺,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 最近的幾個周感覺過得非常的快,明天又是周五了独旷,很快就要期中考試了署穗,現(xiàn)在感覺上課的時間都不夠用了,每節(jié)課都想多講一...
    W王曉紅閱讀 214評論 0 4
  • 第三部分 思考記事本 可以放進①會議相關(guān)的“MGT(meeting)執(zhí)行清單”嵌洼、②分類整理的資料夾案疲、③電話號碼、便...
    115c3253903d閱讀 1,148評論 0 5
  • 越過荒蕪麻养,越過山丘褐啡,時間里我們越走越遠 一場情愛里,男人最初是沖動的鳖昌,女人則是喜歡長久的备畦,真不知我們的故事里,是你...
    桂圓甜甜閱讀 570評論 0 0
  • 連日來莉恼,“馬云已退出阿里旗下5家公司”的網(wǎng)絡(luò)傳言在多家網(wǎng)絡(luò)平臺成為熱議話題。筆者搜索發(fā)現(xiàn)速那,對此阿里專門做出回應俐银,并...
    百姓堂前燕閱讀 270評論 0 0
  • 原文:https://legacy.python.org/dev/peps/pep-3333 PEP:3333標題...
    老周_o_o閱讀 2,402評論 0 5