有人說 Python 不支持函數(shù)重載?
眾所周知,Python 是動態(tài)語言,所謂動態(tài)語言,就是變量的類型是動態(tài)的,程序運行期間變量類型可以隨意變化,由于 Python 的變量是沒有固定類型的,而函數(shù)重載卻依賴變量類型,重載就是定義多個同名函數(shù),但這些同名函數(shù)的參數(shù)類型不同,傳入不同類型的參數(shù)時執(zhí)行與之對應(yīng)的函數(shù)。
Python 的變量沒有類型,因此 Python 語法本身不支持函數(shù)重載,因此有人說 Python 不支持函數(shù)重載這話本身是正確的,不過本文想說的是,Python 動態(tài)語言的靈活性根本不需要通過函數(shù)重載就可以實現(xiàn)一個函數(shù)多個功能。不過要讓 Python 真正支持函數(shù)重載,也就可以的實現(xiàn)的具體來說有兩種方案。
方案一、偽重載
Java 那種重載的好處是從函數(shù)的形式上可以看出函數(shù)支持哪些變量類型,而 Python 由于變量沒有固定的類型,這一點可讀性就不太好,比如說下面的函數(shù) fun,其實是支持兩種參數(shù),一種是全部是字符串,一種是全部都是整數(shù),但是不看代碼的話,其實是看不出來的:
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)
運行結(jié)果
str?x?='hello',?y?=?'world'?
int?x?=?1,?y?=?2
不過好在 Python 有類型提示,借助于 Python 的標準庫 typing,我們也可以寫出重載形式的代碼:
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)
運行結(jié)果:
str?x?='hello',?y?=?'world'?
int?x?=?1,?y?=?2
這樣的話,可讀性就提高了,不過這是一種形式上的重載,真正發(fā)揮作用的是最后那個沒有裝飾器的函數(shù),前面兩個帶裝飾器的函數(shù)只是為了更好的可讀性而存在,沒有實際的作用,可以刪除,不影響程序運行。
要想實現(xiàn) Java 那樣真正的函數(shù)重載,請看方案二。
方案二,借助元類,實現(xiàn)真正的重載
元類是 Python 比較高級的特性,如果一開始就給完整的代碼,你可能看不懂,這里循序漸近的展示實現(xiàn)過程。
Python 中一切皆對象,比如說 1 是 int 的實例,int 是 type 實例:
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) 會產(chǎn)生一個新的類型,也就是說 type(name, bases, dict) 會產(chǎn)生一個 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
上面的代碼,相當于 :
In?[21]:?class?A:
????...:?????pass
????...:
In?[22]:?a?=?A()
In?[23]:?type(a)
Out[23]:?__main__.A
In?[24]:?type(A)
Out[24]:?type
明白了這一點,即使不使用 class 關(guān)鍵字,我們也可以創(chuàng)建出一個類來,比如說下面的 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
請注意上述的 make_A 函數(shù)里面有一個 namespace,它是一個字典,存儲了類的成員變量和成員函數(shù),當我們在一個類中定義多個同名函數(shù)時,最后一個會把前面的全部覆蓋掉,這是字典的特性,同一個鍵多次賦值,只會保留最后一個,因此 Python 類不支持函數(shù)重載。
現(xiàn)在我們需要保留多個同名函數(shù),那就要改寫這個字典,當出現(xiàn)同一個鍵多次賦值時,將這些值(函數(shù))保留在一個列表中,具體方法編寫一個類,繼承 dict,然后編寫代碼如下:
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}"
上述代碼有一個關(guān)鍵的地方,那就是如果有 overload 標識,那么就放在列表 prior_val 中:
elif?isinstance(prior_val,?OverloadList):
????if?not?overloaded:
????????raise?ValueError(self._errmsg(key))
????prior_val.append(value)
其中 OverloadList 就是一個列表,其定義如下:
class?OverloadList(list):
????pass
再寫個裝飾器,標識一個函數(shù)是否要重載:
def?overload(f):
????f.__overload__?=?True
????return?
然后我們來測試下這個 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)
運行結(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ù)的如何保存問題,就是把它們放在一個列表中,還有一個問題沒有解決,那就是調(diào)用的時候如何從列表中取出正確的那個函數(shù)來執(zhí)行?
肯定是根據(jù)函數(shù)傳入的參數(shù)類型作為判斷依據(jù),那如何實現(xiàn)呢?借助于 Python 的類型提示及自省模塊 inspect,當然了,還要借助 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)
這里面有個 Overload 類,它的作用就是將函數(shù)的簽名和定義作一個映射,當我們使用 a.f 時就會調(diào)用 __get__ 方法獲取對應(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__ 返回的是一個 BoundOverloadDispatcher 類,它把參數(shù)類型和對應(yīng)的函數(shù)進行了綁定,只要函數(shù)被調(diào)用時才會調(diào)用 __call__ 返回最匹配的函數(shù)進行調(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 實現(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()
運行結(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
代碼比較長,放在一起不利于閱讀和理解,但全部代碼都在正文中有展示,如果你不想自己組裝,就是想要完整可一鍵運行的代碼,可以關(guān)注公眾號「Python七號」,對話框回復(fù)「重載」獲取實現(xiàn) Python 重載的完整代碼。
