簡體   English   中英

理解 Python 中的位置參數

[英]Understanding positional arguments in Python

對於以下 Python 腳本:

from sys import argv

script, input_encoding, error = argv

def main(language_file, encoding, errors):

    line = language_file.readline()

    if line:

        print_line(line, encoding, errors)
        return main(language_file, encoding, errors)

def print_line(line, encoding, errors):
    next_lang = line.strip()
    raw_bytes = next_lang.encode(encoding, errors=errors)
    cooked_string = raw_bytes.decode(encoding, errors=errors)

    print(raw_bytes, "<===>", cooked_string)

languages = open("languages.txt", encoding="utf-8")

main(languages, input_encoding, error)

查看main功能我不明白以下行:

print_line(line, encoding, errors)

為什么我們調用print_line函數並向其傳遞與其參數名稱完全相同的參數?

print_line()

當我嘗試在不傳遞參數的情況下調用 print_line() 參數時,Python 正在輸出:

print_line() 缺少 3 個必需的位置參數:“line”、“encoding”和“errors”

OP: print_line() 缺少 3 個必需的位置參數:“line”、“encoding”和“errors”

錯誤很明顯,因為它是函數 print_line() 的定義方式。

此外:

def print_line(line, encoding, errors):
    print(line, encoding, errors)


line = 1
encoding = 2
errors = 3

print_line(errors, encoding, line)

輸出:

3 2 1

注意:它是位置參數,而不是命名參數

編輯:1

def abc(a,b,c=2):

return a+b+c

abc(1,2) #both positional argument and c is default

5

abc(2, b=3) # positional, named and again c is default

7

abc(a=2,b=4) # both named argument and c is default

8

編輯2:

OP:位置論證的目的是什么?

好 ..

簡短回答:位置參數是任何未作為key=value對提供的參數。

不幸的是,要理解這意味着什么,有些涉及。

在整個編程社區中,尤其是在 Python 文檔中,術語“參數”的使用有些不准確。

從技術上講,參數是您傳遞給函數的內容,而參數是您定義為這些參數的名稱/占位符的內容。

所以,當我這樣定義一個函數時:

def foo(a,b):
    return a+b

...並像這樣稱呼它:

foo(1,3)

...然后 a 和 b 是我的參數,而 1 和 3 是對該函數的給定調用的參數。

現在這是一個狡辯。 當 a 和 b 實際上是包含函數執行時的參數的名稱(參數)時,人們經常將它們稱為函數的“參數”。

現在,有了這一點,理解 Python 支持四類參數:“必需位置”(您在幾乎任何其他編程語言中看到的那種)、“可選”……或“默認”……位置指定了一些默認值的參數、“star args”(類似於 C/C++ 和 Java 等其他一些語言中的 VARARGS 支持)和“kwargs”。

后者幾乎是 Python 獨有的(盡管 Ruby 有非常相似的支持)。

所以你可以定義一個帶有參數列表的函數,如:

def bar(a, b, c=None, d=[], *e,  **opts):
    '''bar takes all sorts of arguments
    '''
    results = dict()
    results['positional 1'] = a
    results['positional 2'] = b
    results['sum']=a+b
    results['optional'] = list()
    for each in e:
        results['optional'].append(each)
    if c is not None:
        results['default over-ridden']=c
    else:
        results['default']='no arg supplied for parameter c'
    d.append(results)
    if 'verbose' in opts and opts['verbose']:
        for k,v in results:
            print '%s:%s' % (k, v)
    return (results, d)

...這個,相當人為的,例子有幾個正常的,傳統的位置參數(a 和 b),以及可選的第三和第四個參數(如果 bar() 被調用,其中一個將默認為特殊的 Python 單例值None只有兩個參數,另一個默認為列表)以及可選的可變數量的附加參數(通過名為“e”的參數傳遞到我們的函數中),最后, bar() 可以接受任意數量的“關鍵字”參數(作為鍵值對傳遞給它並通過參數“opts”引用(在我的示例中)。

那里有很多東西要消化。 首先有a和b。 這些就像您在大多數編程語言中看到的一樣。 如果你只用一個參數調用它,你會得到一個錯誤,因為該函數需要兩個參數。 然后有 c 和 d ...這些參數可以提供參數...但是如果只使用兩個必需的參數調用函數,那么參數 c 將引用“無”,而 d 將引用列表......這是在我們定義函數時實例化的!

哇! 重新閱讀最后一點,因為對於那些錯誤地使用默認為可變類型(列表、字典、集合或自定義定義類的大多數實例)的參數定義函數的人來說,這是一個常見的混淆源。

當您在 Python 中定義函數時,解釋器正在執行代碼。 它正在執行 def 語句並評估參數(它們成為您函數的參數)。 因此,Python 虛擬機在定義函數時實例化一個列表([] --- 空列表文字)。 參數(在我的示例中為 e)現在綁定到該列表,就像任何 Python“變量”(名稱)綁定到任何其他對象一樣。 並且它所引用的對象可以在任何時候使用三個或更少的參數調用 bar() 時訪問。

這是棘手的一點:任何時候你用三個以上的參數調用 bar 時,參數 e 將被綁定(在那個特定調用的持續時間內)到第四個參數。 在該調用期間,底層列表將被隱藏。 這樣的默認列表對象包含在一個閉包中,通常在函數之外是完全不可訪問的。 (在這個例子中,我在我的 return 語句中包含了一個對它的引用,展示了如果我們選擇,我們可以如何導出對那個封閉對象的引用)。

因此,如果我傳遞 bar() 四個參數,那么它將嘗試使用該對象的“append”方法修改通過其“e”參數引用的對象。 (顯然,這將失敗並為任何不支持 append 方法的對象引發異常)。 每次我只用三個參數調用 bar() 時,我都會在其閉包中修改封閉的列表對象。

這可能很有用,但很少是新程序員在學習 Python 時所期望的。 因此,作為一項規則,不要使用可變對象作為參數的默認值來定義函數。 當您充分理解語義時,您就知道何時打破該規則。

出於某種原因,我使用“無”作為其他參數的默認值。 使用“None”作為您想要為其設置默認值的任何參數的默認值通常很有用,然后顯式測試“is None”並從您的函數體內提供您自己的默認值。 通過這種方式,您可以區分默認參數值和明確傳遞給您的任何參數(並且碰巧與您的默認值匹配)。 (這也將防止您可能無意中創建一個可變閉包的情況,如前所述。在您的函數體中發生的分配/綁定將導致每次調用達到該條件時都進行新的實例化)。

所以我們已經介紹了所需的位置參數,以及那些提供默認值的參數(因此,是可選的位置參數)。

通過參數“e”,我們看到 Python 支持可變數量的參數。 在第四個之后指定的任何參數都將被收集到一個元組中並通過我們的參數 'e' 傳遞給我們......除了以下形式的任何參數:this=that。

這給我們帶來了,最后。 “選擇”。 通常,Python 程序員將此參數稱為“ kwargs ”(關鍵字/字參數)。 我將其命名為“opts”以強調這一點,即“ kwargs ”只是一個傳統的術語,從技術上講,有點令人困惑,因為正如我在整篇文章中所闡述的那樣,它是一個參數,它指的是任何關鍵字參數可能已通過某些函數調用作為參數傳遞。

可以編寫所有函數,使其不接受位置參數,並且僅使用“關鍵字/單詞參數”參數進行定義。 然后,您可以確保每次調用您的函數時,您的函數的調用者必須始終拼出哪個參數綁定到哪個名稱。 如果您的函數在以錯誤順序調用參數的任何情況下可能會產生災難性后果,這可能會很方便。

我意識到這令人困惑……我絕對建議您嘗試使用不同的函數定義和各種調用來查看它們如何相互作用。

我還要注意一個額外的“gotchya”,它可以咬你。 您的調用者不能使用名稱與您的參數名稱沖突的鍵傳遞 kwargs (opts) 值。 嘗試使用參數列表調用 bar() ,例如: bar(1,2,3,4,a=99) ,您將得到如下異常: "TypeError: bar() got multiple values for keyword argument 'a'"

Python 通過管理命名空間(如字典)將參數解析為參數。 試圖提供一個鍵/詞參數,任何與您的參數名稱匹配的鍵都會產生歧義……從而引發異常。

我還將為這個已經很麻煩的答案添加兩個額外的注釋。

當你在 Python 中調用函數時,你可以像這樣傳遞參數:

myargs=(1,2,3)
bar(*myargs)

... 這將被視為您使用bar(1,2,3)調用它 --- 或您進行了以下函數調用: apply(bar, myargs)

事實上,它曾經是,你必須使用apply()函數,以實現間接的這層(回來時,你可以定義與功能*foo參數,但不給他們打電話*foo參數)。

(在函數調用上添加 *args 語法在很大程度上取代了 Python 的 apply() 內置函數的使用)。

...最后,可以使用以下語法傳遞kwargs字典:

mykwargs={'z':99, 'whatever':'yikes'}
bar(1,2,3, **mykwargs)

...或者結合我之前的例子:

bar(*myargs, **mykwargs)

因此,了解定義函數時 * 和 ** 的含義與調用函數時的含義之間的區別至關重要。 如果您了解參數參數之間的區別(盡管術語“參數”更常用),則這些含義是相互補充且直觀的。

為什么我們調用 print_line 函數並向它傳遞與參數名稱完全相同的參數?

這真的只是一個巧合。 以下與您的示例完全相同:

from sys import argv

script, input_encoding, error = argv

def main(language_file, what_encoding_do_you_want, list_of_errors):

    next_line = language_file.readline()

    if next_line:

        print_line(next_line, what_encoding_do_you_want, list_of_errors)
        return main(language_file, what_encoding_do_you_want, list_of_errors)

def print_line(line, encoding, errors):
    next_lang = line.strip()
    raw_bytes = next_lang.encode(encoding, errors=errors)
    cooked_string = raw_bytes.decode(encoding, errors=errors)

    print(raw_bytes, "<===>", cooked_string)

languages = open("languages.txt", encoding="utf-8")

main(languages, input_encoding, error)

這一切都歸結為“范圍”。 print_line 的函數定義聲明它具有三個(位置)參數,它們可以在函數內部稱為“line”、“encoding”和“errors”。

從 main 對 print_line 的函數調用向調用添加了三個參數,這些參數引用現有變量或代碼中該點的參數: line 引用在那里創建的變量; encoding 和 encoding 指的是 main 函數本身的參數。

該函數需要三個參數,通常按指定的順序提供它們。

Python 允許您按照您喜歡的任何順序傳遞它們,但是:

print_line(encoding='ascii', line='hello', errors=None)

甚至

my_dict = {
    'line': 'privet, mir!',
    'errors': None,
    'encoding': 'utf-8'
}
print_line(**my_dict)

但是,該函數需要所有這些參數; 您必須以某種方式傳遞它們,否則您會收到一條錯誤消息,就像您已經注意到的那樣。

變量的作用域是局部的: def任何內容對於它定義的函數都是局部的,並且與該塊之外的任何其他同名變量分開。

通過定義默認值可以使參數成為可選參數。 在 Python 中,可選參數必須始終位於強制參數之后。

def another_fun(confetti, fireworks, bassoons=None, candy='sugar fluff')

如果您僅使用兩個參數調用another_fun ,則bassoons將默認為None並且candy將設置為字符串'sugar fluff'

為什么我們要調用 print_line 函數

好吧,顯然要執行它;-)

並將參數傳遞給它

因為函數被定義為接受參數 - 如果你不提供它需要處理的東西,它就無法完成它的工作。

與它的參數命名完全相同?

這實際上完全無關 - 只是碰巧main()中的變量與函數的參數命名相同(這是有道理的,因為我們在談論相同的事情 - “一行文本”,一個編碼名稱和一個描述如何處理編碼錯誤的值),但是您只傳遞具有任意名稱的文字(未命名)值或變量,它的工作原理相同。

一個函數是一個(主要)獨立的工作單元。 它通常使用一些內部(“本地”)變量,只有它才能看到並且僅在函數執行期間存在。 它通常還需要參數:調用者必須傳遞的值,並綁定到匹配的參數名稱(在我們的例子中,第一個值將綁定到line名稱,第二個值將綁定到encoding名稱等)。 本地名稱(本地變量和參數)與調用者范圍內已知這些值的名稱完全無關(即使它們綁定到名稱 - 正如我所說,您也可以傳遞文字值或其他匿名對象)

當我嘗試在不傳遞參數的情況下調用 print_line() 參數時,Python 輸出“print_line() 缺少 3 個必需的位置參數:'line'、'encoding' 和 'errors'”

是的當然。 該函數需要三個參數,因此您必須傳遞三個參數,簡單明了。 您在調用者作用域( main函數)中有三個同名的局部變量這一事實不會自動為您“填充”這些參數,而且print_line函數無論如何對它的調用者作用域一無所知。

請注意,術語“位置”和“命名”參數主要是指您如何傳遞參數本身 - 按位置(默認值),我已經在上面解釋過,或按名稱(即print_line(line="hello", errors="ignore", encoding="utf-8") ,它允許您以不同的順序傳遞參數,或者從您動態構建的字典等中傳遞參數,但您首先需要了解函數、參數的概念和范圍,然后再進一步......

我強烈建議您閱讀官方教程,其中有一章是關於函數的——它主要面向已經有一些編程經驗的人(它不是 CS101 課程),但它仍然得到了很好的解釋。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM