一日一技:在 Python 中實現函數重載

  • 2019 年 12 月 16 日
  • 筆記

攝影:產品經理

下廚:kingname

假設你有一個函數connect,它有一個參數address,這個參數可能是一個字元串,也可能是一個元組。例如:

connect('123.45.32.18:8080')  connect(('123.45.32.18', 8080))  

你想在程式碼裡面兼容這兩種寫法,於是你可能會這樣寫程式碼:

def connect(address):      if isinstance(address, str):          ip, port = address.split(':')      elif isinstance(address, tuple):          ip, port = address      else:          print('地址格式不正確')  

這種寫法簡單直接,但是如果參數的類型更多,那麼你就需要寫很長的 if-elif-elif-...-else。程式碼看起來就非常不美觀。

學習過 Java 的同學,應該對函數重載比較熟悉,可以定義幾個名字相同的函數,但是他們的參數類型或者數量不同,從而實現不同的程式碼邏輯。

在 Python 裡面,參數的數量不同可以使用默認參數來解決,不需要定義多個函數。那如果參數類型不同就實現不同的邏輯,除了上面的 if-else外,我們還可以使用functools模組裡面的singledispatch裝飾器實現函數重載。

我們來寫一段程式碼:

from functools import singledispatch    @singledispatch  def connect(address):      print(f' 傳輸參數類型為:{type(address)},不是有效類型')    @connect.register  def _(address: str):      ip, port = address.split(':')      print(f'參數為字元串,IP是:{ip}, 埠是:{port}')    @connect.register  def _(address: tuple):      ip, port = address      print(f'參數為元組,IP是:{ip}, 埠是:{port}')    connect('123.45.32.18:8080')  connect(('123.45.32.18', 8080))  connect(123)    

我們運行一下這段程式碼,大家看看根據參數的不同,有什麼樣的不同效果:

可以看到,我們調用的函數,始終都是connect,但是由於傳入參數的類型不同,它運行的結果也不一樣。

我們使用singledispatch裝飾一個函數,那麼這個函數就是我們將會調用的函數。

這個函數在傳入參數不同時的具體實現,通過下面註冊的函數來實現。註冊的時候使用@我們定義的函數名.register來註冊。被註冊的函數名叫什麼無關緊要,所以這裡我都直接使用下劃線代替。

被註冊的函數的第一個參數,通過類型標註來確定它應該使用什麼類型。當我們調用我們定義的函數時,如果參數類型符合某個被註冊的函數,那麼就會執行這個被註冊的函數。如果參數類型不滿足任何一個被註冊的函數,那麼就會執行我們的原函數。

使用類型標註來指定參數類型是從 Python 3.7才引入的新特性。在 Python 3.6或之前的版本,我們需要通過@我們定義的函數名.register(類型)來指定類型,例如:

from functools import singledispatch    @singledispatch  def connect(address):      print(f' 傳輸參數類型為:{type(address)},不是有效類型')    @connect.register(str)  def _(address):      ip, port = address.split(':')      print(f'參數為字元串,IP是:{ip}, 埠是:{port}')    @connect.register(tuple)  def _(address):      ip, port = address      print(f'參數為元組,IP是:{ip}, 埠是:{port}')  

同時,還有一個需要注意的點,就是只有第一個參數的不同類型會被重載。後面的參數的類型變化會被自動忽略。