編程學習網 > 編程語言 > Python > Python函數參數定義中的這兩個分隔符,還有人不知道嗎?
2024
07-09

Python函數參數定義中的這兩個分隔符,還有人不知道嗎?

今天我們來聊下python(3.8+)函數定義中的兩個特殊參數/和*。


前言
python 函數的參數定義想必大家應該是非常熟悉的,有兩種:

位置參數(positional argument):根據函數在參數列表中的位置傳遞給函數的參數。
關鍵詞參數(keyword argument):通過指定參數名稱及其對應值傳參的參數。
def foo(a, b=1, c=2):
    print(a, b, c)
這里的a是位置參數,b 、c是關鍵詞參數。

但是,調用時,我們可以通過多種方式傳參,貌似沒有明確的位置(positional)或關鍵字(keyword)界限:

foo(1, 2, 3)

foo(1, b=2, c=3)
foo(a=1, b=2, c=3)
foo(a=1, c=3, b=2)
foo(c=3, a=1, b=2)
?
請注意:所有位置參數都必須首先出現,然后是關鍵字參數

在Python函數中,參數默認可以按位置(positional)或按關鍵字(keyword)傳入,這意味著調用者可以基于參數的位置或名稱來傳遞值。

然而,在某些情況下,我們可能需要對參數的傳入方式進行限制,以確保函數調用的明確性和正確性。

具體來說,我們可以將某些參數指定為僅位置參數(positional only),這意味著它們只能通過位置來傳入,而不是keyword傳參;

同時,也可以將其他參數指定為僅關鍵字參數(keyword only),這要求它們必須通過關鍵字來指定。

首先,要怎么確定位置參數或關鍵字參數呢?

以下是確定這一點的簡單經驗法則:

核心參數通常是位置參數。核心參數是什么?通常指函數運行所必需的參數。
選項、標志和配置通常是關鍵字。這些參數通常不是函數的核心參數,而是修改函數的行為方式。
這看起來有點抽象,所以讓我們看一個具體的例子。以下是標準庫中shutil模塊中copyfile函數的函數簽名。

def copyfile(src, dst, *, follow_symlinks=True)
顧名思義,此功能將文件從一個地方復制到另一個地方。因此,src和dst參數是函數的核心。

此外,還有一個標志follow_symlinks。

圖片
此標志不是函數操作的核心部分,但它改變了函數的行為方式。因此,此參數應為關鍵字參數。

這樣的區分有助于避免參數的混淆,并提供了更精確的函數調用傳參方式。

我們發現,copyfile函數簽名中有個*參數,這又是什么呢?這將強制follow_symlinks只能關鍵字傳參(keyword only)。

特殊參數
python3.8 之后,python 引入了一個新的函數定義語法,你可以使用Special parameters(/和*)將你函數的位置參數和關鍵字參數分開,即可以強制執行位置參數或強制關鍵字參數。

圖片
一圖勝千言。

簡而言之就是:

位于斜杠/之前的參數被指定為僅位置參數(positional only),這意味著它們必須按照在函數定義中出現的順序傳入,不能使用關鍵字參數的形式。
位于星號*之后的位置則被保留給僅關鍵字參數(keyword only),調用者必須使用關鍵字來指定這些參數。
至于/和*之間的參數,它們遵循默認的行為,既可以通過位置傳入,也可以通過關鍵字傳入。
當然,在函數的定義中,這二者你也可以只用一個,或者都不用,或者兩個都用。
用法示例
Python 中的強制位置函數參數/的用法

所有/之前的參數都是positional-only參數,這意味著它們只能作為位置參數傳遞給函數,而不能作為關鍵字參數傳遞。

一般,核心參數,或在參數順序比參數名稱更重要的情況下,或者參數的名字本身沒什么語義(開發者希望在未來可以隨時修改這個參數的名字),例如在處理圖像或執行幾何計算時,此功能非常有用。通過使用僅位置參數,僅需確保以正確的順序傳參調用函數,從而提高代碼的清晰度和可維護性。

def positional_only(a, b, /):
    print(a + b)


positional_only(1, 2)
對于上面這個函數而言,調用positional_only函數時,參數a、b只能是位置參數,即:positional_only(1, 2)執行正確。

而positional_only(1, b=2)和positional_only(a=1, b=2)將執行錯誤。

# 執行將報錯:TypeError: positional_only() got some positional-only arguments passed as keyword arguments: 'b'
# positional_only(1, b=2)

# 執行將報錯:TypeError: positional_only() got some positional-only arguments passed as keyword arguments: 'a, b'
# positional_only(a=1, b=2) 
Python 中的強制關鍵字函數參數*的用法

所有*之后的參數都是keyword-only參數,它們只能作為關鍵字參數傳遞給函數,不能作為位置參數傳遞。

當您想要強制對某些參數(例如具有默認值的可選參數)使用關鍵字參數時,或者當您想要明確區分必需參數和可選參數時,又或者這些參數在未來的位置也隨時可能發生變化(在前面加入新的參數之類),此功能非常有用。

def keyword_only(a, b, *, c):
    print(a + b + c)


keyword_only(1, 2, c=7)
keyword_only(1, b=2, c=7)
keyword_only(a=1, b=2, c=7)
對于上面這個函數而言,調用keyword_only函數時,強制參數c只能是關鍵字(keyword-only)參數傳參;a、b不做限制,屬于positional or keyword傳參。

/和*都出現在函數參數中

def f(a, /, b, *, c):
    print(a + b + c)


f(1, 2, c=7)  # 10
f(1, b=2, c=7)  # 10
# f(a=1, b=2, c=7)  # wrong, TypeError: f() got some positional-only arguments passed as keyword arguments: 'a'
從上面可知,強制a是positional_only參數,b(不做限制)是positional or keyword參數,強制c屬于keyword-only參數。

內置函數中的強制位置參數和強制關鍵字參數

此外,這種語法也在 Python 內置函數的定義和文檔說明中得到了應用,體現了其在官方實踐中的規范性和重要性。如我們熟知的exec內置函數的函數簽名:

圖片
exec(object, globals=None, locals=None, /, *, closure=None)
 # help(exec) 可查看函數簽名
對于此函數,參數object和globals、locals必須作為位置傳遞,僅因為它們出現在/的左側。另一方面,closure是一個僅關鍵字參數,因為它出現在*的右側。

def keyword_only(*, a, b, c):
    print(a + b + c)


exec("keyword_only(a=1, b=2, c=7)")
關于函數簽名, exec需要注意的一件有趣的事情是globals和locals具有默認值。因此,傳遞這些選項不是強制性的。但是,如果您確實為這些參數傳入了值,那么它必須是位置的。顯然,python 核心開發人員認為這兩個參數是exec函數的核心部分。

再比如sorted函數:

圖片
sorted(iterable, /, *, key=None, reverse=False)
iterable必須以positional-only的形式傳入,而兩個可選參數key和reverse,若傳必須以keyword-only的形式傳入。

data = {'apple': 2, 'orange': 4, 'banana': 1}
# 按字典的值排序
sorted_data_by_value = sorted(data.items(), key=lambda item: item[1], reverse=True)
print(sorted_data_by_value)  # 輸出: [('orange', 4), ('apple', 2), ('banana', 1)]
小結
要指定函數調用者僅能使用位置參數,您可以將這些參數放置在斜杠/之前。這樣,調用者在傳遞這些參數時必須按照它們在函數定義中出現的順序,而不能使用關鍵字參數。

相反,如果您希望確保調用者在使用函數時必須顯式地指定某些參數,您可以將這些參數放置在星號*之后。這要求調用者在調用函數時使用關鍵字參數來傳遞這些值,從而避免了依賴于參數的位置,提高了代碼的可讀性和明確性。

在設計函數時,如果您面臨需要明確區分參數傳入方式的語義需求,不妨考慮采用這兩個分隔符/和 *。這種明確的參數定義不僅有助于減少因參數誤用而導致的錯誤,還能增強程序的健壯性和可靠性,從而提升整體的代碼質量和維護性。

以上就是Python函數參數定義中的這兩個分隔符,還有人不知道嗎?的詳細內容,想要了解更多Python教程歡迎持續關注編程學習網。

掃碼二維碼 獲取免費視頻學習資料

Python編程學習

查 看2022高級編程視頻教程免費獲取