有人說(shuō) Python 不支持函數(shù)重載?
眾所周知,Python 是動(dòng)態(tài)語(yǔ)言,所謂動(dòng)態(tài)語(yǔ)言,就是變量的類型是動(dòng)態(tài)的,程序運(yùn)行期間變量類型可以隨意變化,由于 Python 的變量是沒(méi)有固定類型的,而函數(shù)重載卻依賴變量類型,重載就是定義多個(gè)同名函數(shù),但這些同名函數(shù)的參數(shù)類型不同,傳入不同類型的參數(shù)時(shí)執(zhí)行與之對(duì)應(yīng)的函數(shù)。
Python 的變量沒(méi)有類型,因此 Python 語(yǔ)法本身不支持函數(shù)重載,因此有人說(shuō) Python 不支持函數(shù)重載這話本身是正確的,不過(guò)本文想說(shuō)的是,Python 動(dòng)態(tài)語(yǔ)言的靈活性根本不需要通過(guò)函數(shù)重載就可以實(shí)現(xiàn)一個(gè)函數(shù)多個(gè)功能。不過(guò)要讓 Python 真正支持函數(shù)重載,也就可以的實(shí)現(xiàn)的具體來(lái)說(shuō)有兩種方案。
方案一、偽重載
Java 那種重載的好處是從函數(shù)的形式上可以看出函數(shù)支持哪些變量類型,而 Python 由于變量沒(méi)有固定的類型,這一點(diǎn)可讀性就不太好,比如說(shuō)下面的函數(shù) fun,其實(shí)是支持兩種參數(shù),一種是全部是字符串,一種是全部都是整數(shù),但是不看代碼的話,其實(shí)是看不出來(lái)的:
def?fun(x,?y):
????if?isinstance(x,?str)?and?isinstance(y,?str):
????????print(f"str?{x?=},?{y?=?}?")
????elif?isinstance(x,?int)?and?isinstance(y,?int):
????????print(f"int?{x?=?},?{y?=?}")
fun("hello",?"world")
fun(1,2)
運(yùn)行結(jié)果
str?x?='hello',?y?=?'world'?
int?x?=?1,?y?=?2
不過(guò)好在 Python 有類型提示,借助于 Python 的標(biāo)準(zhǔn)庫(kù) typing,我們也可以寫(xiě)出重載形式的代碼:
import?typing
class?A:
[email protected]
????def?fun(self,?x:?str,?y:?str)?->?None:
????????pass
[email protected]
????def?fun(self,?x:?int,?y:?int)?->?None:
????????pass
????def?fun(self,?x,?y)?->?None:
????????if?isinstance(x,?str)?and?isinstance(y,?str):
????????????print(f"str?{x?=},?{y?=?}?")
????????elif?isinstance(x,?int)?and?isinstance(y,?int):
????????????print(f"int?{x?=?},?{y?=?}")
if?__name__?==?"__main__":
????a?=?A()
????a.fun("hello",?"world")
????a.fun(1,?2)
運(yùn)行結(jié)果:
str?x?='hello',?y?=?'world'?
int?x?=?1,?y?=?2
這樣的話,可讀性就提高了,不過(guò)這是一種形式上的重載,真正發(fā)揮作用的是最后那個(gè)沒(méi)有裝飾器的函數(shù),前面兩個(gè)帶裝飾器的函數(shù)只是為了更好的可讀性而存在,沒(méi)有實(shí)際的作用,可以刪除,不影響程序運(yùn)行。
要想實(shí)現(xiàn) Java 那樣真正的函數(shù)重載,請(qǐng)看方案二。
方案二,借助元類,實(shí)現(xiàn)真正的重載
元類是 Python 比較高級(jí)的特性,如果一開(kāi)始就給完整的代碼,你可能看不懂,這里循序漸近的展示實(shí)現(xiàn)過(guò)程。
Python 中一切皆對(duì)象,比如說(shuō) 1 是 int 的實(shí)例,int 是 type 實(shí)例:
In?[7]:?a?=?5
In?[8]:?type(a)
Out[8]:?int
In?[9]:?type(int)
Out[9]:?type
In?[10]:
In?[11]:?type??
Init?signature:?type(self,?/,?*args,?**kwargs)
Docstring:
type(object_or_name,?bases,?dict)
type(object)?->?the?object's?type
type(name,?bases,?dict)?->?a?new?type
Type:???????????type
Subclasses:?????ABCMeta,?EnumMeta,?_TemplateMetaclass,?_ABC,?MetaHasDescriptors,?NamedTupleMeta,?_TypedDictMeta,?LexerMeta,?StyleMeta,?_NormalizerMeta,?...
從上述可以看出,type(object) 返回 object 的類型,而 ?type(name, bases, dict) 會(huì)產(chǎn)生一個(gè)新的類型,也就是說(shuō) type(name, bases, dict) 會(huì)產(chǎn)生一個(gè) class:
In?[17]:?A?=?type('A',(),{})
In?[18]:?a?=?A()
In?[19]:?type(a)
Out[19]:?__main__.A
In?[20]:?type(A)
Out[20]:?type
上面的代碼,相當(dāng)于 :
In?[21]:?class?A:
????...:?????pass
????...:
In?[22]:?a?=?A()
In?[23]:?type(a)
Out[23]:?__main__.A
In?[24]:?type(A)
Out[24]:?type
明白了這一點(diǎn),即使不使用 class 關(guān)鍵字,我們也可以創(chuàng)建出一個(gè)類來(lái),比如說(shuō)下面的 make_A() 和 A() 的作用是一樣的:
class?A:
????a?=?1
????b?=?"hello"
????def?fun(self):
????????return?"Class?A"
def?make_A():
????name?=?'A'
????bases?=?()
????a?=?1
????b?=?"hello"
????def?fun():
????????return?"Class?A"
????namespace?=?{'a':a,'b':b,'fun':?fun}
????return?type(name,bases,namespace)
if?__name__?==?'__main__':
????a?=?A()
????print(a.b)
????print(a.fun())
????print("==="*5)
????b?=?make_A()
????print(b.b)
????print(b.fun())
執(zhí)行結(jié)果:
hello
Class?A
===============
hello
Class?A
請(qǐng)注意上述的 make_A 函數(shù)里面有一個(gè) namespace,它是一個(gè)字典,存儲(chǔ)了類的成員變量和成員函數(shù),當(dāng)我們?cè)谝粋€(gè)類中定義多個(gè)同名函數(shù)時(shí),最后一個(gè)會(huì)把前面的全部覆蓋掉,這是字典的特性,同一個(gè)鍵多次賦值,只會(huì)保留最后一個(gè),因此 Python 類不支持函數(shù)重載。
現(xiàn)在我們需要保留多個(gè)同名函數(shù),那就要改寫(xiě)這個(gè)字典,當(dāng)出現(xiàn)同一個(gè)鍵多次賦值時(shí),將這些值(函數(shù))保留在一個(gè)列表中,具體方法編寫(xiě)一個(gè)類,繼承 dict,然后編寫(xiě)代碼如下:
class?OverloadDict(dict):
????def?__setitem__(self,?key,?value):
????????assert?isinstance(key,?str),?"keys?must?be?str"
????????prior_val?=?self.get(key,?_MISSING)
????????overloaded?=?getattr(value,?"__overload__",?False)
????????if?prior_val?is?_MISSING:
????????????insert_val?=?OverloadList([value])?if?overloaded?else?value
????????????super().__setitem__(key,?insert_val)
????????elif?isinstance(prior_val,?OverloadList):
????????????if?not?overloaded:
????????????????raise?ValueError(self._errmsg(key))
????????????prior_val.append(value)
????????else:
????????????if?overloaded:
????????????????raise?ValueError(self._errmsg(key))
????????????super().__setitem__(key,?value)
????@staticmethod
????def?_errmsg(key):
????????return?f"must?mark?all?overloads?with?@overload:?{key}"
上述代碼有一個(gè)關(guān)鍵的地方,那就是如果有 overload 標(biāo)識(shí),那么就放在列表 prior_val 中:
elif?isinstance(prior_val,?OverloadList):
????if?not?overloaded:
????????raise?ValueError(self._errmsg(key))
????prior_val.append(value)
其中 OverloadList 就是一個(gè)列表,其定義如下:
class?OverloadList(list):
????pass
再寫(xiě)個(gè)裝飾器,標(biāo)識(shí)一個(gè)函數(shù)是否要重載:
def?overload(f):
????f.__overload__?=?True
????return?
然后我們來(lái)測(cè)試下這個(gè) OverloadDict,看看它產(chǎn)生的效果:
print("OVERLOAD?DICT?USAGE")
d?=?OverloadDict()
@overload
def?f(self):
????pass
d["a"]?=?1
d["a"]?=?2
d["b"]?=?3
d["f"]?=?f
d["f"]?=?f
print(d)
運(yùn)行結(jié)果:
OVERLOAD?DICT?USAGE
{'a':?2,?'b':?3,?'f':?[<function?overload_dict_usage..f?at?0x7fdec70090d0>,?<function?overload_dict_usage..f?at?0x7fdec70090d0>]}
OverloadDict 解決了重名函數(shù)的如何保存問(wèn)題,就是把它們放在一個(gè)列表中,還有一個(gè)問(wèn)題沒(méi)有解決,那就是調(diào)用的時(shí)候如何從列表中取出正確的那個(gè)函數(shù)來(lái)執(zhí)行?
肯定是根據(jù)函數(shù)傳入的參數(shù)類型作為判斷依據(jù),那如何實(shí)現(xiàn)呢?借助于 Python 的類型提示及自省模塊 inspect,當(dāng)然了,還要借助 Python 的元類:
class?OverloadMeta(type):
????@classmethod
????def?__prepare__(mcs,?name,?bases):
????????return?OverloadDict()
????def?__new__(mcs,?name,?bases,?namespace,?**kwargs):
????????overload_namespace?=?{
????????????key:?Overload(val)?if?isinstance(val,?OverloadList)?else?val
????????????for?key,?val?in?namespace.items()
????????}
????????return?super().__new__(mcs,?name,?bases,?overload_namespace,?**kwargs)
這里面有個(gè) Overload 類,它的作用就是將函數(shù)的簽名和定義作一個(gè)映射,當(dāng)我們使用 a.f 時(shí)就會(huì)調(diào)用 __get__ 方法獲取對(duì)應(yīng)的函數(shù)。其定義如下:
class?Overload:
????def?__set_name__(self,?owner,?name):
????????self.owner?=?owner
????????self.name?=?name
????def?__init__(self,?overload_list):
????????if?not?isinstance(overload_list,?OverloadList):
????????????raise?TypeError("must?use?OverloadList")
????????if?not?overload_list:
????????????raise?ValueError("empty?overload?list")
????????self.overload_list?=?overload_list
????????self.signatures?=?[inspect.signature(f)?for?f?in?overload_list]
????def?__repr__(self):
????????return?f"{self.__class__.__qualname__}({self.overload_list!r})"
????def?__get__(self,?instance,?_owner=None):
????????if?instance?is?None:
????????????return?self
????????#?don't?use?owner?==?type(instance)
????????#?we?want?self.owner,?which?is?the?class?from?which?get?is?being?called
????????return?BoundOverloadDispatcher(
????????????instance,?self.owner,?self.name,?self.overload_list,?self.signatures
????????)
????def?extend(self,?other):
????????if?not?isinstance(other,?Overload):
????????????raise?TypeError
????????self.overload_list.extend(other.overload_list)
????????self.signatures.extend(other.signatures)
__get__ 返回的是一個(gè) BoundOverloadDispatcher 類,它把參數(shù)類型和對(duì)應(yīng)的函數(shù)進(jìn)行了綁定,只要函數(shù)被調(diào)用時(shí)才會(huì)調(diào)用 __call__ 返回最匹配的函數(shù)進(jìn)行調(diào)用:
class?BoundOverloadDispatcher:
????def?__init__(self,?instance,?owner_cls,?name,?overload_list,?signatures):
????????self.instance?=?instance
????????self.owner_cls?=?owner_cls
????????self.name?=?name
????????self.overload_list?=?overload_list
????????self.signatures?=?signatures
????def?best_match(self,?*args,?**kwargs):
????????for?f,?sig?in?zip(self.overload_list,?self.signatures):
????????????try:
????????????????bound_args?=?sig.bind(self.instance,?*args,?**kwargs)
????????????except?TypeError:
????????????????pass??#?missing/extra/unexpected?args?or?kwargs
????????????else:
????????????????bound_args.apply_defaults()
????????????????#?just?for?demonstration,?use?the?first?one?that?matches
????????????????if?_signature_matches(sig,?bound_args):
????????????????????return?f
????????raise?NoMatchingOverload()
????def?__call__(self,?*args,?**kwargs):
????????try:
????????????f?=?self.best_match(*args,?**kwargs)
????????except?NoMatchingOverload:
????????????pass
????????else:
????????????return?f(self.instance,?*args,?**kwargs)
????????#?no?matching?overload?in?owner?class,?check?next?in?line
????????super_instance?=?super(self.owner_cls,?self.instance)
????????super_call?=?getattr(super_instance,?self.name,?_MISSING)
????????if?super_call?is?not?_MISSING:
????????????return?super_call(*args,?**kwargs)
????????else:
????????????raise?NoMatchingOverload()
????????????
def?_type_hint_matches(obj,?hint):
????#?only?works?with?concrete?types,?not?things?like?Optional
????return?hint?is?inspect.Parameter.empty?or?isinstance(obj,?hint)
def?_signature_matches(sig:?inspect.Signature,?bound_args:?inspect.BoundArguments):
????#?doesn't?handle?type?hints?on?*args?or?**kwargs
????for?name,?arg?in?bound_args.arguments.items():
????????param?=?sig.parameters[name]
????????hint?=?param.annotation
????????if?not?_type_hint_matches(arg,?hint):
????????????return?False
????return?True
到這里已經(jīng)差不多了,我們組裝一下上面的代碼,就可以讓 Python 實(shí)現(xiàn)真正的重載:
import?inspect
class?NoMatchingOverload(Exception):
????pass
_MISSING?=?object()
class?A(metaclass=OverloadMeta):
????@overload
????def?f(self,?x:?int):
????????print("A.f?int?overload",?self,?x)
????@overload
????def?f(self,?x:?str):
????????print("A.f?str?overload",?self,?x)
????@overload
????def?f(self,?x,?y):
????????print("A.f?two?arg?overload",?self,?x,?y)
class?B(A):
????def?normal_method(self):
????????print("B.f?normal?method")
????@overload
????def?f(self,?x,?y,?z):
????????print("B.f?three?arg?overload",?self,?x,?y,?z)
????#?works?with?inheritance?too!
class?C(B):
????@overload
????def?f(self,?x,?y,?z,?t):
????????print("C.f?four?arg?overload",?self,?x,?y,?z,?t)
def?overloaded_class_example():
????print("OVERLOADED?CLASS?EXAMPLE")
????a?=?A()
????print(f"{a=}")
????print(f"{type(a)=}")
????print(f"{type(A)=}")
????print(f"{A.f=}")
????a.f(0)
????a.f("hello")
????#?a.f(None)?#?Error,?no?matching?overload
????a.f(1,?True)
????print(f"{A.f=}")
????print(f"{a.f=}")
????b?=?B()
????print(f"{b=}")
????print(f"{type(b)=}")
????print(f"{type(B)=}")
????print(f"{B.f=}")
????b.f(0)
????b.f("hello")
????b.f(1,?True)
????b.f(1,?True,?"hello")
????#?b.f(None)??#?no?matching?overload
????b.normal_method()
????c?=?C()
????c.f(1)
????c.f(1,?2,?3)
????c.f(1,?2,?3,?4)
????#?c.f(None)?#?no?matching?overload
def?main():
????overloaded_class_example()
if?__name__?==?"__main__":
????main()
運(yùn)行結(jié)果如下:
OVERLOADED?CLASS?EXAMPLE
a=<__main__.A?object?at?0x7fbabe67d8e0>
type(a)='__main__.A'>
type(A)='__main__.OverloadMeta'>
A.f=Overload([<function?A.f?at?0x7fbabe679280>,?<function?A.f?at?0x7fbabe679310>,?<function?A.f?at?0x7fbabe6793a0>])
A.f?int?overload?<__main__.A?object?at?0x7fbabe67d8e0>?0
A.f?str?overload?<__main__.A?object?at?0x7fbabe67d8e0>?hello
A.f?two?arg?overload?<__main__.A?object?at?0x7fbabe67d8e0>?1?True
A.f=Overload([<function?A.f?at?0x7fbabe679280>,?<function?A.f?at?0x7fbabe679310>,?<function?A.f?at?0x7fbabe6793a0>])
a.f=<__main__.BoundOverloadDispatcher?object?at?0x7fbabe67d910>
b=<__main__.B?object?at?0x7fbabe67d910>
type(b)='__main__.B'>
type(B)='__main__.OverloadMeta'>
B.f=Overload([<function?B.f?at?0x7fbabe6794c0>])
A.f?int?overload?<__main__.B?object?at?0x7fbabe67d910>?0
A.f?str?overload?<__main__.B?object?at?0x7fbabe67d910>?hello
A.f?two?arg?overload?<__main__.B?object?at?0x7fbabe67d910>?1?True
B.f?three?arg?overload?<__main__.B?object?at?0x7fbabe67d910>?1?True?hello
B.f?normal?method
A.f?int?overload?<__main__.C?object?at?0x7fbabe67d9a0>?1
B.f?three?arg?overload?<__main__.C?object?at?0x7fbabe67d9a0>?1?2?3
C.f?four?arg?overload?<__main__.C?object?at?0x7fbabe67d9a0>?1?2?3?4
代碼比較長(zhǎng),放在一起不利于閱讀和理解,但全部代碼都在正文中有展示,如果你不想自己組裝,就是想要完整可一鍵運(yùn)行的代碼,可以關(guān)注公眾號(hào)「Python七號(hào)」,對(duì)話框回復(fù)「重載」獲取實(shí)現(xiàn) Python 重載的完整代碼。
