<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 9970字,需瀏覽 20分鐘

           ·

          2021-10-26 21:33

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

          瀏覽 99
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚州免费高清 | 日本免费成人A | 熟女十区| 欧美第18页| 国内精品久久久久久久久久 |