1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        有人說(shuō) Python 不支持函數(shù)重載?

        共 9970字,需瀏覽 20分鐘

         ·

        2021-10-26 21:33

        眾所周知,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 重載的完整代碼。

        瀏覽 99
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            大香蕉天天干 | 丁香五月久久 | 成人无码福利视频 | 亚州无码AV | 国产视频18 | 精品成人一区二区三区 | 美女的裙底隐私网站 | 国产亲妺妺乱的性视频 | 一级老女人特黄A片视频 | 豆花视频官方网站入口在线观看 |