1. 程式人生 > 實用技巧 >【Python 1-14】Python手把手教程之——詳解函式的高階用法

【Python 1-14】Python手把手教程之——詳解函式的高階用法

作者 | 弗拉德
來源 | 弗拉德(公眾號:fulade_me)

傳遞列表

你經常會發現,向函式傳遞列表很有用,這種列表包含的可能是名字、數字或更復雜的物件(如字典)。將列表傳遞給函式後,函式就能直接訪問其內容。下面使用函式來提高處理列表的效率。

假設有一個使用者列表,我們要問候其中的每位使用者。下面的示例將一個名字列表傳遞給一個名為greet_users()的函式,這個函式問候列表中的每個人:

def greet_users(names): 
    """向列表中的每位使用者都發出簡單的問候""" 
    for name in names:
        msg = "Hello, " + name.title() + "!" 
        print(msg)
        
usernames = ['hannah', 'ty', 'margot'] 
greet_users(usernames)

我們將greet_users()定義成接受一個名字列表,並將其儲存在形參names中。這個函式遍歷收到的列表,並對其中的每位使用者都列印一條問候語。然後我們定義了一個使用者列表——usernames,然後呼叫greet_users(),並將這個列表傳遞給它:

Hello, Hannah! 
Hello, Ty! 
Hello, Margot!

輸出完全符合預期,每位使用者都看到了一條個性化的問候語。每當你要問候一組使用者時,都可呼叫這個函式。

在函式中修改列表

將列表傳遞給函式後,函式就可對其進行修改。在函式中對這個列表所做的任何修改都是永久性的,這讓你能夠高效地處理大量的資料。

來看一家為使用者提交的設計製作3D列印模型的公司。需要列印的設計儲存在一個列表中,列印後移到另一個列表中。下面是在不使用函式的情況下模擬這個過程的程式碼:

# 首先建立一個列表,其中包含一些要列印的設計
unprinted_designs = ['iphone case', 'robot pendant', 'dodecahedron'] 
completed_models = []
# 模擬列印每個設計,直到沒有未列印的設計為止
# 列印每個設計後,都將其移到列表completed_models中 
while unprinted_designs:
    current_design = unprinted_designs.pop()
    #模擬根據設計製作3D列印模型的過程 
    print("Printing model: " + current_design) completed_models.append(current_design)
# 顯示列印好的所有模型
print("\nThe following models have been printed:") 
for completed_model in completed_models:
    print(completed_model)

這個程式首先建立一個需要列印的設計列表,還建立一個名為completed_models的空列表, 每個設計列印都將移到這個列表中。

只要列表unprinted_designs中還有設計,while迴圈就模擬列印設計的過程:從該列表末尾刪除一個設計,將其儲存到變數current_design中,並顯示一條訊息,指出正在列印當前的設計,再將該設計加入到列表completed_models中。迴圈結束後,顯示已列印的所有設計:

Printing model: dodecahedron 
Printing model: robot pendant
Printing model: iphone case 

The following models have been printed: 
dodecahedron
robot pendant
iphone case

為重新組織這些程式碼,我們可編寫兩個函式,每個都做一件具體的工作。大部分程式碼都與原來相同,只是效率更高。第一個函式將負責處理列印設計的工作,而第二個將概述列印了哪些設計:

def print_models(unprinted_designs, completed_models):
    """
    模擬列印每個設計,直到沒有未列印的設計為止列印每個設計後,都將其移到列表completed_models中
    """
    while unprinted_designs:
        current_design = unprinted_designs.pop()
        # 模擬根據設計製作3D列印模型的過程
        print("Printing model: " + current_design)
        completed_models.append(current_design)

def show_completed_models(completed_models): 
    """顯示列印好的所有模型"""
    print("\nThe following models have been printed:") for completed_model in completed_models:
        print(completed_model)
unprinted_designs = ['iphone case', 'robot pendant', 'dodecahedron'] 
completed_models = []

print_models(unprinted_designs, completed_models) 
show_completed_models(completed_models)

首先,我們定義了函式print_models(),它包含兩個形參:一個需要列印的設計列表和一個列印好的模型列表。

給定這兩個列表,這個函式模擬列印每個設計的過程:將設計逐個地從未 列印的設計列表中取出,並加入到列印好的模型列表中。

然後,我們定又義了函式show_completed_models(),它包含一個形參:列印好的模型列表。給定這個列表,函式show_completed_models()顯示打印出來的每個模型的名稱。
這個程式的輸出與未使用函式的版本相同,但組織更為有序。完成大部分工作的程式碼都移到了兩個函式中,讓主程式更容易理解。只要看看主程式,你就知道這個程式的功能容易看清得多:

unprinted_designs = ['iphone case', 'robot pendant', 'dodecahedron'] 
completed_models = []
print_models(unprinted_designs, completed_models) 
show_completed_models(completed_models)

我們建立了一個未列印的設計列表,還建立了一個空列表,用於儲存列印好的模型。接下來,由於我們已經定義了兩個函式,因此只需呼叫它們並傳入正確的實參即可。我們呼叫print_models()並向它傳遞兩個列表;像預期的一樣,print_models()模擬列印設計的過程。接下來,我們呼叫show_completed_models(),並將列印好的模型列表傳遞給它,讓其能夠指出列印了哪些模型。描述性的函式名讓別人閱讀這些程式碼時也能明白,雖然其中沒有任何註釋。

相比於沒有使用函式的版本,這個程式更容易擴充套件和維護。如果以後需要列印其他設計,只需再次呼叫print_models()即可。如果我們發現需要對列印程式碼進行修改,只需修改這些程式碼一次,就能影響所有呼叫該函式的地方;與必須分別修改程式的多個地方相比,這種修改的效率更高。

這個程式還演示了這樣一種理念,即每個函式都應只負責一項具體的工作。第一個函式列印每個設計,而第二個顯示列印好的模型;這優於使用一個函式來完成兩項工作。編寫函式時,如果你發現它執行的任務太多,請嘗試將這些程式碼劃分到兩個函式中。別忘了,總是可以在一個函 數中呼叫另一個函式,這有助於將複雜的任務劃分成一系列的步驟。

傳遞任意數量的實參

有時候,你預先不知道函式需要接受多少個實參,好在Python允許函式從呼叫語句中收集任意數量的實參。

例如,來看一個製作披薩的函式,它需要接受很多配料,但你無法預先確定顧客要多少種配料。下面的函式只有一個形參*toppings,但不管呼叫語句提供了多少實參,這個形參都將它們統統收入囊中:

def make_pizza(*toppings): 
    """列印顧客點的所有配料""" 
    print(toppings)
    make_pizza('pepperoni')
    make_pizza('mushrooms', 'green peppers', 'extra cheese')

形參名*toppings中的星號讓Python建立一個名為toppings的空元組,並將收到的所有值都封裝到這個元組中。函式體內的print語句通過生成輸出來證明Python能夠處理使用一個值呼叫函式的情形,也能處理使用三個值來呼叫函式的情形。
它以類似的方式處理不同的呼叫,注意,Python將實參封裝到一個元組中,即便函式只收到一個值也如此:

('pepperoni',)
('mushrooms', 'green peppers', 'extra cheese')

現在,我們可以將這條print語句替換為一個迴圈,對配料列表進行遍歷,並對顧客點的披薩進行描述:

def make_pizza(*toppings):
    """概述要製作的比薩"""
    print("\nMaking a pizza with the following toppings:") 
    for topping in toppings:
        print("- " + topping)
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

不管收到的是一個值還是三個值,這個函式都能妥善地處理:

Making a pizza with the following toppings: 
- pepperoni
Making a pizza with the following toppings: 
- mushrooms
- green peppers
- extra cheese

不管函式收到的實參是多少個,這種語法都管用。

將函式儲存在模組中

函式的優點之一是,使用它們可將程式碼塊與主程式分離。通過給函式指定描述性名稱,可讓主程式容易理解得多。你還可以更進一步,將函式儲存在被稱為模組的獨立檔案中,再將模組匯入到主程式中。import語句允許在當前執行的程式檔案中使用模組中的程式碼。
通過將函式儲存在獨立的檔案中,可隱藏程式程式碼的細節,將重點放在程式的高層邏輯上。這還能讓你在眾多不同的程式中重用函式。將函式儲存在獨立檔案中後,可與其他程式設計師共享這些檔案而不是整個程式。知道如何匯入函式還能讓你使用其他程式設計師編寫的函式庫。
匯入模組的方法有多種,下面對每種都作簡要的介紹。

匯入整個模組

要讓函式是可匯入的,得先建立模組。模組是副檔名為.py的檔案,包含要匯入到程式中的程式碼。下面來建立一個包含函式make_pizza()的模組。為此,我們將檔案pizza.py中除函式make_pizza()之外的其他程式碼都刪除:

def make_pizza(size, *toppings):
    """概述要製作的比薩"""
    print("\nMaking a " + str(size) + "-inch pizza with the following toppings:")
    for topping in toppings:
        print("- " + topping)

接下來,我們在pizza.py所在的目錄中建立另一個名為making_pizzas.py的檔案,這個檔案匯入剛建立的模組,再呼叫make_pizza()兩次:

import pizza
pizza.make_pizza(16, 'pepperoni')
pizza.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

Python讀取這個檔案時,程式碼行import pizza讓Python開啟檔案pizza.py,並將其中的所有函式都複製到這個程式中。你看不到複製的程式碼,因為這個程式執行時,Python在幕後複製這些程式碼。你只需知道,在making_pizzas.py中,可以使用pizza.py中定義的所有函式。

要呼叫被匯入的模組中的函式,可指定匯入的模組的名稱pizza和函式名make_pizza(),並用句點分隔它們。這些程式碼的輸出與沒有匯入模組的原始程式相同:

Making a 16-inch pizza with the following toppings:
- pepperoni
Making a 12-inch pizza with the following toppings: 
- mushrooms
- green peppers
- extra cheese

這就是一種匯入方法:只需編寫一條import語句並在其中指定模組名,就可在程式中使用該模組中的所有函式。如果你使用這種import語句匯入了名為module_name.py的整個模組,就可使用下面的語法來使用其中任何一個函式:

module_name.function_name()

匯入特定的函式

你還可以匯入模組中的特定函式,這種匯入方法的語法如下:

from module_name import function_name

通過用逗號分隔函式名,可根據需要從模組中匯入任意數量的函式:

from module_name import function_0, function_1, function_2

對於前面的making_pizzas.py示例,如果只想匯入要使用的函式,程式碼將類似於下面這樣:

from pizza import make_pizza
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

若使用這種語法,呼叫函式時就無需使用句點。由於我們在import語句中顯式地匯入了函式make_pizza(),因此呼叫它時只需指定其名稱。

使用as給函式指定別名

如果要匯入的函式的名稱可能與程式中現有的名稱衝突,或者函式的名稱太長,可指定簡短而獨一無二的別名——函式的另一個名稱,類似於外號。要給函式指定這種特殊外號,需要在匯入它時這樣做。
下面給函式make_pizza()指定了別名mp()。這是在import語句中使用make_pizza as mp實現的,關鍵字as將函式重新命名為你提供的別名:

from pizza import make_pizza as mp
mp(16, 'pepperoni')
mp(12, 'mushrooms', 'green peppers', 'extra cheese')

上面的import語句將函式make_pizza()重新命名為mp();在這個程式中,每當需要呼叫make_pizza()時,都可簡寫成mp(),而Python將執行make_pizza()中的程式碼,這可避免與這個程式可能包含的函式make_pizza()混淆。指定別名的通用語法如下:

from module_name import function_name as fn

使用as給模組指定別名

你還可以給模組指定別名。通過給模組指定簡短的別名(如給模組pizza指定別名p),讓你能夠更輕鬆地呼叫模組中的函式。相比於pizza.make_pizza()p.make_pizza()更為簡潔:

import pizza as p
p.make_pizza(16, 'pepperoni')
p.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

上述import語句給模組pizza指定了別名p,但該模組中所有函式的名稱都沒變。呼叫函式make_pizza()時,可編寫程式碼p.make_pizza()而不是pizza.make_pizza(),這樣不僅能使程式碼更簡潔,還可以讓你不再關注模組名,而專注於描述性的函式名。這些函式名明確地指出了函式的功能,對理解程式碼而言,它們比模組名更重要。 給模組指定別名的通用語法如下:

import module_name as mn

匯入模組中的所有函式

使用星號(*)運算子可讓Python匯入模組中的所有函式:

from pizza import *
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

import語句中的星號讓Python將模組pizza中的每個函式都複製到這個程式檔案中。由於匯入了每個函式,可通過名稱來呼叫每個函式,而無需使用句點表示法。然而,使用並非自己編寫的大型模組時,最好不要採用這種匯入方法:如果模組中有函式的名稱與你的專案中使用的名稱相同,可能導致意想不到的結果:Python可能遇到多個名稱相同的函式或變數,進而覆蓋函式,而不是分別匯入所有的函式。

最佳的做法是,要麼只匯入你需要使用的函式,要麼匯入整個模組並使用句點表示法。這能讓程式碼更清晰,更容易閱讀和理解。這裡之所以介紹這種匯入方法,只是想讓你在閱讀別人編寫 的程式碼時,如果遇到類似於下面的import語句,能夠理解它們:

from module_name import *

函式編寫指南

編寫函式時,需要牢記幾個細節。應給函式指定描述性名稱,且只在其中使用小寫字母和下 劃線。描述性名稱可幫助你和別人明白程式碼想要做什麼。給模組命名時也應遵循上述約定。
每個函式都應包含簡要地闡述其功能的註釋,該註釋應緊跟在函式定義後面,並採用文件字 符串格式。文件良好的函式讓其他程式設計師只需閱讀文件字串中的描述就能夠使用它:他們完全 可以相信程式碼如描述的那樣執行;只要知道函式的名稱、需要的實參以及返回值的型別,就能在 自己的程式中使用它。
給形參指定預設值時,等號兩邊不要有空格:

def function_name(parameter_0, parameter_1='default value')

對於函式呼叫中的關鍵字實參,也應遵循這種約定:

function_name(value_0, parameter_1='value')

官方建議程式碼行的長度不要超過79字元,這樣只要編輯器視窗適中,就能看到整行程式碼。如果形參很多,導致函式定義的長度超過了79字元,可在函式定義中輸入左括號後按回車鍵,並在下一行按兩次Tab鍵,從而將形參列表和只縮排一 層的函式體區分開來。
大多數編輯器都會自動對齊後續引數列表行,使其縮排程度與你給第一個引數列表行指定的 縮排程度相同:

def function_name(parameter_0, parameter_1, parameter_2, parameter_3, parameter_4, parameter_5):
    function body...

如果程式或模組包含多個函式,可使用兩個空行將相鄰的函式分開,這樣將更容易知道前一 個函式在什麼地方結束,下一個函式從什麼地方開始。所有的import語句都應放在檔案開頭,唯一例外的情形是,在檔案開頭使用了註釋來描述整個程式。