Python學習之路7-函數

本系列是對入門書籍《Python編程:從入門到實戰》的筆記整理,屬於初級內容。標題順序採用書中標題

1. 定義函數

1.1 一般函數

函數是帶名字的代碼塊,該代碼塊是完成特定工作的固定代碼序列。如果程序中多次出現相同或相似的代碼塊,則應將這段代碼提取出來,編寫成函數,然後多次調用。通過編寫函數可以避免重複工作,使程序的編寫、閱讀、測試和修復更容易。請使用描述性的函數名來命名函數,以大致表明函數的功能,這樣即使沒有註釋也能容易理解。函數名應儘量只有小寫字母和下劃線。以下是兩個最基本的函數,有參數與無參函數:

Python學習之路7-函數

在調用函數前,必須先定義函數!即函數的定義部分必須在調用語句之前。

上述代碼中的三引號字符串叫做文檔字符串,他們既可以被用作代碼註釋,也可用於自動生成有關程序中函數的文檔。

實參和形參

這兩個概念經常被搞混,函數定義中的參數叫做形參,比如上述函數greet_user2(username)中的username就是形參;傳遞給函數的參數叫做實參,比如在調用greet_user2(“jesse”)時的“jesse”就是實參。

1.2 空函數

如果想定義一個什麼都不做的函數,可以使用pass語句。

Python學習之路7-函數

如果為了讓程序能跑起來,但暫時又不寫這個函數,可以使用pass語句。這裡pass用作佔位符。

2. 傳遞參數

2.1 位置參數(必選參數)

這就是要求實參的順序和形參的順序相同。

Python學習之路7-函數

對於位置參數,應該注意實參的傳遞順序,如果順序不對,結果會出乎意料:有可能報錯,如果不報錯,函數所要表達的意思可能改變。

Python學習之路7-函數

2.2 關鍵字參數(傳實參時)

如果函數的形參過多,則很難記住每個位置的參數是用來幹什麼的,如果用鍵值對的方式傳遞實參,這個問題就能迎刃而解,這就是關鍵字參數。在傳遞參數時,直接將形參與實參關聯,這樣就不用在意實參的位置,依然以上述代碼為例,函數定義不變:

Python學習之路7-函數

請注意,這是一種傳遞參數的方法!在調用函數時使用!

2.3 默認參數(定義函數時,形參)

編寫函數時可以為每個形參指定默認值,給形參指定了默認值之後,在調用函數時可以省略相應的實參。使用默認值可以簡化函數調用,也可清楚地指出函數的典型用法。比如上述describe_pet()函數如果給形參animal_type指定默認值"dog",則可以看出這個函數主要是用來描述狗這種寵物的。

Python學習之路7-函數

在函數調用時,如果給形參提供了實參,Python將使用指定的實參;否則將使用形參的默認值。

注意:默認參數是在函數定義時使用!在定義函數時帶有默認值的形參必須在沒有默認值的形參後面!

還有一點值得注意:默認參數必須指向不變對象!請看以下代碼:

Python學習之路7-函數

當給這個函數傳遞了參數時,結果是正確的,而且,在沒有傳遞參數且第一次調用時,返回結果也是正確的,然而,沒有傳遞參數且第二次、第三次調用時,結果則成了問題。這是因為,Python在函數定義的時候,默認參數的值就被計算了出來,形參只要不指向新的值,它就會一直指向這個默認值,但如果這個默認值是個可變對象,就會出現上述情況。

要修正上述例子,可以使用None, str之類的不變對象。如下:

Python學習之路7-函數

補充–設計不變對象的原因:

①對象一旦創建則不可修改,可以減少因修改數據而產生的錯誤;

②由於對象不可修改,在多任務環境下不需要加鎖,同時讀不會出錯。所以,我們在設計一個對象時,能設計成不變對象則設計成不變對象。

3. 返回值

3.1 返回簡單值

函數並非總是直接顯示輸出,它可以處理一些數據並返回一個或一組值。在Python的函數中,使用return語句來返回值。以下是一個參數可選的帶有返回值的函數例子:

Python學習之路7-函數

3.2 返回字典

Python函數可以返回任何類型的值,包括列表和字典等複雜的數據結構。

Python學習之路7-函數

3.3 返回多個值

return語句後面用逗號分隔多個值,則可返回多個值:

Python學習之路7-函數

但其實這是個假象,其實函數返回的是一個元組(Tuple),只是最後對元組進行了解包,然後對a,b進行了平行賦值。

Python學習之路7-函數

如果函數返回多個值,但有些值並不想要,則這些位置的值可以用單下劃線進行接收:

Python學習之路7-函數

4. 傳遞列表

將列表傳遞給函數,函數可以直接訪問其內容或對其進行修改。用函數處理列表可以提高效率。

以下代碼是一個打印程序,將未打印的設計在打印後轉移到另一個列表中,此代碼中未使用函數:

Python學習之路7-函數

現在用兩個函數來重組這些代碼:

Python學習之路7-函數

從以上代碼可以看出,使用了函數後,主程序變為了短短四行。

相比於沒有使用函數的代碼,使用了函數後代碼更易讀也更容易維護。

在編寫函數時,儘量每個函數只負責一項功能,如果一個函數負責的功能太多,應將其分成多個函數。同時,函數里面還能調用另一個函數;函數里也能再定義函數!

禁止函數修改列表:

有時候需要禁止函數修改列表,以上述代碼為例,print_models()函數在執行完成後清空了未打印列表unprinted_design,但有時我們並不希望這個列表被清空,而是留作備案。為解決此問題,可以向函數傳遞副本而不是原件,如下:

Python學習之路7-函數

如果從C/C++的角度來看(沒有研究過Python底層代碼,這裡僅是猜測),實參unprinted_designs是一個指針,當他傳遞給函數時,形參得到了這個變量的一個拷貝,形參也指向了內存中的那片區域,所以能直接修改。而當使用切片傳遞拷貝時,Python先在內存中複製一遍實參unprinted_designs指向的數據,並給這片數據的地址賦給一個臨時的變量,然後再將這個臨時變量傳遞給形參。

5. 傳遞任意數量的參數

5.1 結合使用位置參數(必選參數)和任意數量參數(*args)

有時候你並不知道要向函數傳遞多少個參數,比如製作披薩,你不知道顧客要多少種配料,此時使用帶一個星號(*)的形參,來定義函數:

Python學習之路7-函數

從結果可以看出,以可變參數的方式傳入值時,Python將值封裝成了一個元組,即使是隻傳入了一個值。

補充:多個參數都在一個列表裡面,如果一個元素一個元素的傳遞,則代碼會很難看,可以使用如下方式傳遞參數,任以上述make_pizza()函數為例:

Python學習之路7-函數

在後面的“任意關鍵字參數”小節中,也可用這種方式傳值,只不過得用雙星號(**)。

注意:如果要讓函數接收不同類型的參數,必須將可變參數放在最後,因為Python先匹配位置參數和關鍵字參數,再將剩餘的參數封裝到最後一個可變參數中。

Python學習之路7-函數

5.2 使用任意數量的關鍵字參數(**kw)

有時候需要傳入任意數量的參數,並且還要知道這些參數是用來幹什麼的,此時函數需要能夠接受任意數量的關鍵字參數,這裡使用雙星號(**)來實現:

Python學習之路7-函數

此處代碼太長,後面沒有截完,但不影響理解

從上述結果可以看出,Python將任意關鍵字參數封裝成一個字典。這裡也要注意,指示任意關鍵字參數的形參必須放到最後!

區分---命名關鍵字參數(也叫命名參數)

上述代碼可以傳遞任意數量的關鍵字參數,但有時需要限制傳入的關鍵字參數,比如上述build_profile()函數除了傳入first和last這兩個必選參數之外,還必須且只能傳入age和country這兩個參數(一個不多,一個不少)時,則需要用到命名關鍵字參數,它使用一個星號分隔必選參數和命名關鍵字參數,如下:

Python學習之路7-函數

從以上結果可以看出命名關鍵字參數必須每個都賦值,可以有默認值,有默認值的可以不用再賦值;命名關鍵字之間可以交換順序,如果要和前面的必選參數也交換順序,則必須使用關鍵字參數的方式傳遞實參。

為什麼有命名關鍵字參數:

(網上搜的答案,個人暫時認為這種參數可以被位置參數給替換掉)命名參數配合默認參數使用可以簡化代碼,比如在寫類的構造函數時,有10個參數,8個有合理的默認值,那麼可以將這8個定義為命名關鍵字參數,前兩個就是必須賦值的位置參數。這樣,在後面生成對象時,如果要替換默認值:

①要麼按順序給後面8個參數替換默認值(C++做法);

②要麼用關鍵字參數的傳值方式給這8個關鍵字不一定按順序來賦值(Python做法);

③要麼混合①②的做法,不過容易混淆。(也就是全用必選參數,前面一部分按順序賦值,後面一部分用關鍵字參數賦值)

一點感想:但如果是筆者自己寫代碼,暫時更偏向於全用必選參數,帶默認值,即如下定義形式:

Python學習之路7-函數

而不是如下形式:

Python學習之路7-函數

可能筆者才疏學淺,暫時還沒領會到這種方式的精髓之處。

不過上述是沒有可變參數的情況,如果是以如下形式定義函數:

Python學習之路7-函數

在以如下形式調用時則會報錯:

Python學習之路7-函數

可以看出,Python在這裡將test4解釋為了位置參數,但筆者是想將其作為可變參數。所以筆者推測,在以下情況時,使用命名關鍵字參數比較好:

必選參數數量不少(其中有些參數的默認值不常變動),後面又跟有可變參數,由於必選參數很多,不容易記住位置,如果不用命名參數,按照上述關鍵字方式調用函數則會出錯,所以此時將這些有合理默認值的必選參數變為命名關鍵字參數,則可以使用關鍵字參數不按順序傳值。但如果沒有可變參數時,筆者還是傾向於使用帶默認值的必選參數。

還有一點值得注意:命名關鍵字參數可以和可變參數(*args)混用,此時語法如下:

Python學習之路7-函數

這裡c, d為命名關鍵字參數,並且前面也不用加單個星號進行區分了,但是,如果和可變數量關鍵字參數(**kw)進行混用,命名關鍵字不能在可變數量關鍵字參數之前,即不存在如下函數定義形式:

Python學習之路7-函數

如果這樣定義,Pycharm會標紅(其他IDE沒用過,不知道提不提示)。

綜上所述:Python中一共有五中參數類型,即必選參數(位置參數),默認參數(帶默認值的參數),可變參數(*args),命名關鍵字參數和關鍵字參數(數量可變,**kw),這五種可以同時混用,但是必須遵照如下順序:

(從左到右)必選參數、默認參數、可變參數、命名關鍵字參數和關鍵字參數

。以下是這兩個參數混用的幾個例子:

Python學習之路7-函數

常用的包含任意數量關鍵字,且不區分參數類型的函數定義方式如下

Python學習之路7-函數

6. 將函數存儲在模塊(Module)中

在python中,一個.py文件就是一個模塊。使用模塊的最大好處就是提高了代碼的可維護性。其次,代碼不用從零開始編寫,一個模塊編寫完成後,可以在其他地方被調用。再次,可以避免函數名和變量名衝突,不同模塊可以有相同的函數名和變量名。

6.1 導入整個模塊

要讓函數是可以導入的,得先創建模塊。以上述make_pizza()函數為例,將其餘代碼刪掉,只保留這一個函數,然後再在當前目錄中創建一個making_pizzas.py的文件,執行如下代碼以導入整個模塊:

Python學習之路7-函數

以這種方式導入模塊時,按如下方式調用函數:

Python學習之路7-函數

6.2 導入某模塊中特定的函數

語法結構為:

Python學習之路7-函數

仍以上述pizza.py為例:

Python學習之路7-函數

6.3 模塊補充

別名

當函數名發生衝突,或者函數名、模塊名太長時,可以取一個簡短的名稱,類似“外號”,以上述代碼為例:

Python學習之路7-函數

導入模塊中的所有函數

例如導入pizza模塊中的所有函數:

Python學習之路7-函數

然而,使用並非自己編寫的大型模塊時,最好不要採用這種導入方法,因為如果模塊中有函數或變量和你自己寫的函數、變量同名,結果將有問題。所以,一般的做法是要麼只導入你需要的函數,要麼導入整個模塊並用句點表示法。

包:

Python中的包就是一個文件夾,但這個文件夾下面必須包含名為“__init__.py”的文件(前後都是雙下劃線),包中可以放多個模塊,組織結構與Java包類似。


分享到:


相關文章: