Python 與 C / C++ 簽章

學習辨認 Python 與 C / C++ 的函式呼叫方式。

簽章

函式 (Function) 能在特定需求時輸入資料與輸出結果,輸入與輸出值稱為參數 (Argument)。

以強型別程式語言 C 來說,一個輸入 3 個參數,輸出 1 個參數的函式是這樣宣告的:

int func(int p0, int p1, float p2);

但是 Python 沒有類型標示,因此函式會如此定義:

def func(p0, p1, p2):
    ...

函式 func 的輸入與輸出類型(如「輸入 3 個參數、輸出 1 個參數」),稱為簽章 (Signature)。

Python 是弱型別語言,簽章只有強制規定參數數量,而沒有規定參數類型。

另外,單一化的輸入需求不足以滿足函式應用範圍,而產生多載 (Overload) 的功能,以支援不同選項、不同類型的輸入。以強型別程式語言 C++ 為例:

// 注意:C 語言不支援多載。
// C++ 多載宣告,編譯時會依照簽章自動生成四種函式。
// 預設值也算是一種多載。
// 撰寫程式時只需要寫 func 即可。
int func(int p0, int p1, long double p2, bool p3 = false);
int func(int p0, int p1, double p2) {
    // 會需要寫轉換型別的接繞函式。
    // 如果簽章搞錯,會造成無限遞迴 (Recursive)。
    return func(p0, p1, (long double) p2);
}
int func(int p0, int p1, float p2) {
    // 會需要調整設定。
    return func(p0, p1, (long double) p2, true);
}

上面的例子中,為了支援強型別的標記,函式必須製作多種不同類型的簽章,若有更多需求,則必須使用模板 (Template):

// 模板宣告,編譯時會自動生成多載簽章。
template <typename T>
int func(int p0, int p1, T p2);
// 如果要支援其他參數,又要混合多載。
int func(int p0, int p1, long double p2, bool p3 = false);

在 Python 中,沒有多載沒有模板沒有型別要求,因此大幅簡化定義。唯一要解決的問題是滿足參數數量的變化。

預設值

預設值只能從最後開始使用等於 = 符號連續定義,不可以隨意指定其中的參數。

在呼叫時使用 = 符號為關鍵字參數,與定義時相同,只能從最後開始連續填入。

def func(p0, p1, p2=20, p3=True):
    pass

# 可以這樣使用:
func(a0, a1, a2, a3)  # 全部填滿。
func(a0, a1, a2)  # 使用部分預設值。
func(a0, a1, p3=a3)  # 名稱對應,使用部分預設值。
func(a0, a1)  # 全部使用預設值。

強制名稱對應

使用特殊符號 *,其後所有參數不分順序,強制使用名稱對應。如果有參數沒有填入或沒有預設值,會引發錯誤。

def func(p0, p1, *, p2, p3=True):
    pass

# 可以這樣使用:
func(a0, a1, p2=a2, p3=a3)  # 全部填滿。
func(a0, a1, p2=a2)  # 名稱對應,全部使用預設值。

順序收集與對應收集

  • 使用星號 *,該變數會收集多餘填入的位置參數 (Positional argument)。多餘的參數會收集成 tuple 容器,若沒有多餘參數,該參數會是空的 tuple 容器。
  • 使用雙星號 **,該變數會收集多餘填入的關鍵字參數 (Keyword argument)。多餘的參數會收集成 dict 容器,若沒有多餘參數,該參數會是空的 dict 容器。

兩者可以擇一或混合使用,但是定義時單星號參數一定要在前面,其後所有參數不分順序。

def func(p0, p1, *p2, **p3, p4, p5=True):
    pass

# 可以這樣使用:
func(a0, a1, p4=a4, p5=a5)  # 全部填滿。
# p2 = ()
# p3 = {}
func(a0, a1, p4=a4)  # 全部使用預設值。
# p2 = ()
# p3 = {}
func(a0, a1, b1, b2, r=b3, s=b4, p4=a3)  # 多餘值。
# p2 = (b1, b2)
# p3 = {'r': b3, 's': b4}

針對「順序容器(如 tuplelist)」或「成對容器(如 dict)」,填入時可以用 *** 按關係填入。不過不符合簽章仍然會引發錯誤,應當注意。如:

func(*[a0, a1, b1, b2], **{'r':b3, 's': b4, 'p4': a3})
# 或用變數:
a_list = [a1, b1, b2]
b_dict = {'r':b3, 'p4': a3}
func(a0, s=b4, *a_list, **b_dict)
Show Comments