(請先看置頂博文)https://blog.csdn.net/GenuineMonster/article/details/104495419
目錄
(請先看置頂博文)https://blog.csdn.net/GenuineMonster/article/details/104495419
?
目錄
目錄
(請先看置頂博文)https://blog.csdn.net/GenuineMonster/article/details/104495419
一、定義函數
二、傳遞實參
1、位置實參
2、關鍵字實參
3、默認值
4、避免實參錯誤
三、函數的返回值
1、返回簡單值
2、返回字典
四、傳遞列表
1、在函數中對列表進行修改
2、禁止函數修改列表
五、傳遞任意數量的實參
1、結合位置實參,使用任意數量實參。(位置實參和任意數量實參共用)
2、結合關鍵之字實參,使用任意數量實參。(關鍵字實參和任意數量實參共用)
六、函數與模塊的關系
1、導入模塊中的特定函數
2、使用as為函數、模塊重命名
3、使用*號導入模塊中的所有函數
4、函數編寫指南
?
?
?
?
?
函數的特點:
1、函數可將程序分成多個部分,每部分都負責完成一項具體的任務,也可存放到單獨的文件中。
2、代碼分塊化、函數化后,更加易于維護、調用、重用和排除故障。
所以在這一節里我們將學習函數的定義、參數類型、如何傳參等內容,上一節課也說了,這部分內容相比之前的難度增大了許多,所以要反復理解、及時復習。
一、定義函數
定義函數的關鍵字是def,在def后要寫這個函數的名字,最好起一個和這個函數實現功能相關的函數名,方便閱讀。最后增加圓括號(有的函數需要參數,下面會講)和冒號,函數聲明的第一步就完成了。下面給出一個簡單函數的例子,包含對函數代碼的備注:
# 定義一個具有問候功能的函數
# 使用關鍵字def告訴編譯器接下來的代碼塊是一個函數
"""顯示簡單的問候語"""
def greet_user():print("Hello!") #打印hellogreet_user() # 調用greet_user函數
冒號后面縮進的程序構成了函數體,實現函數的主要功能,此處只有一句打印問候語。調用函數也十分簡單,只需要在合適的位置依次指定函數名以及括號括起的必要信息(就是參數)。定義函數語句的上一行有被三個雙引號括住的漢字,這個被稱為“文檔字符串的注釋”(在我看來就是段備注),Python編譯器會使用三引號括起的文檔字符串生成有關程序中函數的文檔。上面這個函數是最簡單的函數結構,接下來的函數都是以這個結構為原型進行拓展的。
我們現在做一些改動,將這段程序的函數變得復雜一丟丟。在函數定義def greet_user()的括號內增加username(這就是所謂的函數參數),這樣我們就可以將代碼改為:
# 定義一個具有問候功能的函數
# 使用關鍵字def告訴編譯器接下來的代碼塊是一個函數
"""顯示簡單的問候語"""
def greet_user(username):print("Hello! " + username.title() + ". ") #打印hello+用戶姓名greet_user('jesse') # 調用greet_user函數
因為定義函數時我們加入了參數,所以我們在調用時也得增加內容,函數將會把我們在調用時增加的內容傳遞給參數“username”,然后使用新的print()語句打印出對某個人的問候語。說到這,就要說一下參數的類型:形參和實參。形參就是我們在聲明函數時在括號內寫的的“username”,可以將其理解為形式上的參數;而實參就是我們上一段代碼中調用函數時在括號中輸入的“jesse”。(形參:函數完成其工作所需的一項信息;實參:實參是調用函數時傳遞給函數的信息)(還有:這個實參形參只是一個稱謂,如果有人混淆了,也不要大驚小怪,我們理解那個意思就好!)
二、傳遞實參
鑒于函數定義中可能包含多個形參,所以在函數調用時,也必須包含多個實參。(或者說:調用函數時,Python編譯器必須將函數調用中的每個實參都關聯到函數定義中的一個形參)傳遞實參的方式很多,可使用位置實參(要求實參的順序與形參的順序相同)。也可以使用關鍵字實參,其中每個實參都由變量名和值組成。還可以用列表和字典。
1、位置實參
位置實參是最簡單的關聯(傳參)方式,為明白其中的原理,我們用一個示例代碼來解釋一下。下面的代碼是一個顯示寵物信息的函數。這個函數指明寵物的種類和名稱:
def describe_pet(animal_type, pet_name):"""顯示寵物信息"""print("\nI have a " + animal_type + ". ")print("My " + animal_type + "'s name is " + pet_name.title() + ". ")
describe_pet('hasmster','harry')
上面這段代碼定義的函數包含兩個形參,分別是動物類型和寵物名稱。那么,我們在調用這個函數時,使用位置實參的方法,按照形參的順序,依次輸入“hasmster”和“harry”,就可以將這兩個實參分別與形參一一對應起來,最后成功打印出來“我有什么(動物類型)以及我的(寵物類型)的(寵物名稱)是什么”。(特別提示:位置實參的順序很重要)同樣,我們可以更換“hasmster”和“harry”對應的內容,重新調用已定義的函數。由此,定義函數的意義也就出來了:高效,只需要寫一次代碼,改變實參的內容,就可以在接下來的代碼中多次使用。
2、關鍵字實參
關鍵字實參是傳遞給函數的名稱—值對。直接在實參中將名稱和值關聯起來,因此向函數傳遞實參時不會混淆。關鍵字實參的優點是:無需考慮函數調用中的實參順序,并且能清楚地指出函數調用中各個值的用途。那么,接下來的代碼,我將會使用關鍵字實參演示:
我們觀察可以看到,describe_pet()函數的定義部分沒有變,變得只是調用函數時的傳參方式(關鍵字實參)。一旦使用關鍵字的實參,那么實參的順序就無關緊要了,因為等號已經指定了實參與形參的對應關系。所以以下兩種調用函數代碼的輸出結果是一致的。
describe_pet(animal_type = 'hasmster',pet_name = 'harry')
describe_pet(pet_name = 'harry',animal_type = 'hasmster')
3、默認值
我們在定義函數時,可以給每個形參指定默認值。在調用函數中給形參提供實參時,Python將使用指定的實參值;否則,將使用形參的默認值。因此,給形參指定默認值后,可在函數調用中省略相應的參數。使用默認值的優點是:可簡化函數調用,還可清楚的指出函數的典型用法。例如,發現調用函數describe_pet()時,描述的大都是小狗,就可將形參animal_type的默認值設為‘dog’,如此一來,再調用函數描述小狗時,就可以不用提供這種信息了。例如:
def describe_pet(pet_name,animal_type='dog'):"""顯示寵物信息"""print("I have a " + animal_type + ". ")print("My " + animal_type + "'s name is " + pet_name.title() + ". ")
print("位置實參:")
describe_pet('harry')
print("關鍵字實參:")
describe_pet(pet_name = 'harry')
print("默認值是否可修改驗證:")
describe_pet(pet_name = 'harry',animal_type = 'cat')
上面這段代碼為寵物類型指定了形參的默認值,那么我們只需要對寵物名稱進行傳參即可。我分別使用了兩種傳參方式,并且驗證了一下“寵物類型雖然指定默認值,但仍舊可以傳參的事實”。細心的同學觀察到:上述代碼在定義函數時,修改了形參的排列順序(與之前的代碼相比較)。假設,我們沒有對形參的順序進行修改,并且使用的是“位置實參”。如果此時調用函數,僅僅指定動物名稱,那么就會出現下面的錯誤:
這是因為寵物類型已經被指定,我們在調用函數時,Python編譯器將會把第一個實參與第一個形參關聯起來。所以,定義函數和調用函數要一致。另外,在使用默認值時,在形參列表中必須先列出沒有默認值的形參,再列出有默認值的形參。如此一來,Python編譯器能夠正確解讀位置實參。那么按照紅字原則定義出來的函數將會有多種調用函數的方法,無論哪種方法,只要函數調用能生成期待的輸出就行。
4、避免實參錯誤
開始使用函數之后,我們可以犯的錯誤又多了很多種。其中一種是實參錯誤:我們在調用函數時所提供的實參多于或少于函數完成其工作所需的信息時,將出現實參不匹配的錯誤。我們舉個例子,解釋一下,并且詳細講解一下traceback的報錯信息。
def desribe_pet(animal_type,pet_name):"""顯示寵物的信息"""print("\nI have a " + animal_type + ". ")print("My " + animal_type + "'s name is " + pet_name.tiltle() + ". ")
describe_pet()
?在1處:traceback指出了問題出現在什么地方;在2處,指出了導致問題的函數調用;在3處,traceback指出該函數調用少了兩個參數,并指出了相應形參的名稱。如果提供的實參多了,traceback也會出現類似的錯誤提示。
三、函數的返回值
1、返回簡單值
?完成上面的函數定義、函數傳參之后,我們進行函數的返回。通過上面的例代碼,我們發現確實定義了函數、確實調用函數(傳遞參數)、也確實見到了對應結果的輸出,但是怎么沒看見返回值?這是因為我們上面的函數例子都是無返回值的函數。函數收到實參后進行的處理工作并非都是直接輸出,它可能也要處理一些數據什么的,然后返回一個或一組值,所以函數返回的值就被稱為返回值。編程語言中(Python、C、C++)用return語句將值返回到調用函數的代碼行。下面通過一個例子進行詳細的說明:
def get_formatted_name(first_name,last_name):"""返回整潔的姓名"""full_name = first_name + ' ' + last_namereturn full_name.title()
musician = get_formatted_name('jimi','hendrix')
print(musician)
解釋:函數名為get_formatted_name,在定義時有兩個形參分別是first_name,last_name。當函數被調用且受到兩個實參時,代碼將會讓“名字”和“姓氏”重新組合為一個完整的姓名,然后將其存儲在full_name中,使用return語句返回完整姓名(開頭大寫)。這個full_name的值會返回到調用函數get_formatted()的那一行代碼里,并將返回值賦給musician,最后使用print語句將完整的名字輸出。值得注意的是在調用有返回值的函數時,需要為其提供一個變量,存儲返回值。可能有同學就會詢問了,我們明明能很簡單的將這個名字完整的輸出,為什么要寫一個如此繁瑣的程序?別急,往下看(手動滑稽)
2、返回字典
函數可返回任何類型的值,包括列表和字典等較復雜的數據結構。如下例代碼所示:
def build_person(first_name,last_name):# 返回一個字典,其中包含有關一個人的信息person = {'first':first_name,'last':last_name}return person
musician = build_person('jimi','hendrix')
print(musician)
函數build_person()接受名和姓,并將這些值封裝到字典里。我們還可以擴展這個函數,增加其可接受的值,如下例代碼所示:(在函數定義時,我們新增了一個可選形參age,空字符串,將其作為備選項)
def build_person(first_name,last_name,age=''):# 返回一個字典,其中包含有關一個人的信息person = {'first':first_name,'last':last_name}if age: #如果調用函數時由年齡,那么就把年齡存儲起來,一起輸出;若沒有,直接輸出person['age'] = agereturn person
musician = build_person('jimi','hendrix',age = 27)
print(musician)
下面是沒有年齡時的輸出:
在本小節的最后,我們將函數與while循環結合起來,寫一個問候用戶的程序,從而更正規的問候用戶。
def get_formatted_name(first_name,last_name):# 返回整潔的姓名full_name = first_name + ' '+last_namereturn full_name.title()# 接下來是一個無限不循環代碼片段
while True:print("\nPlease tell me your name:")f_name = input("First name:")l_name = input("last name:")formatted_name = get_formatted_name(f_name,l_name)print("\nHello, " + formatted_name + "!")
四、傳遞列表
向函數傳遞列表很有用,列表包含的可能是名字、數字或更復雜的對象(如字典)。將列表傳遞給函數后,函數就能直接訪問了。下面給出一個例子:將名字列表傳遞給一個名為greet_users()的函數,這個函數問候列表中的每一個人。
?
def greet_users(names): # 這里的names是形參"""向列表中的每位用戶都發出簡單的問候"""for name in names: # 函數處理時按列表處理即可(直接傳參數就行)msg = "Hello, " + name.title() + "!"print(msg)usernames = ['hannah','try','margot'] # 名字列表
greet_users(usernames) # 這里的usernames是實參
1、在函數中對列表進行修改
將列表傳遞給函數后,函數就可對其進行修改。在函數中對這個列表所做的任何修改都是永久的,如此一來就能夠高效的處理大量的數據了。有如下例代碼,初級版本是這樣的:
# 需要打印的列表
unprinted_designs = ['iphone case','robot pendant','dodecahedron']
# 打印完的列表
completed_models = []# 模擬打印每個設計,直到沒有未打印的設計為止
# 打印每個設計后,都將其移到列表compled_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)
但是上述代碼,在處理大量數據時,效率不怎么高。所以,我們將在接下來對其進行改進,使其效率更高。我們設計兩個函數,讓其分別進行一項具體的工作。第一個函數負責打印設計的工作,第二個函數將概述打印了哪些設計。
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)
這兩段代碼的輸出結果一樣,但是后者組織的更有序,大部分工作的代碼轉移到了這兩個函數中,讓主程序更加簡約、容易理解。除此以外,這段代碼更容易被維護和擴展。通過上述代碼,可以學習一種編碼原則:每個函數都應負責一項具體的工作。除此之外,一個函數還可以調用另一個函數。
2、禁止函數修改列表
? ? ? ?
? ? ? ?函數能修改列表,這已經是事實了。有些時候,我們得禁止函數修改列表。還是打印的例子,比如我們在打印完成后,依然需要保留原來的打印列表。此時,我們可以這樣解決:可向函數傳遞列表的副本而不是原件,這樣函數所做的任何修改都只會影響副本而不影響原件。
? ? ? ?調用函數使用副本的代碼如下所示:
print_models(unprinted_designs[:],completed_models)
不知道你們還是否知道上述代碼中“[ :]”這個代表什么,這個是之前學習的切片,這里表示創建原列表的副本。雖然可以向函數傳遞列表的副本可保留原始列表的內容,但除非是有充分的理由需要傳遞副本,否則還是應該將原始列表傳遞給函數。(因為讓函數使用現成的列表可避免花時間和內存創建副本,從而提高代碼效率。即時間復雜度和空間復雜度)
五、傳遞任意數量的實參
有時候在寫函數時,不知道將會有多少個實參傳遞進來,此時也不用去構思。因為Python允許函數從調用語句中收集任意數量的實參。如下例代碼:
def make_pizza(*toppings): # 帶*號的意思是創建一個名為toppings的空元組,將收到的所有值都封裝到這個元組內"""打印顧客點的所有配料"""print(toppings)make_pizza('pepperoni')
make_pizza('mushrooms','green peppers','extra cheese')
在成功嘗試傳遞任意數量的實參之后,接下來可以把函數中的print重新改寫,要對配料進行遍歷,并一一輸出。
def make_pizza(*toppings): # 帶*號的意思是創建一個名為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')
由上述實驗可以看出,無論函數收到的實參個數有多少個,這種語法都管用。
1、結合位置實參,使用任意數量實參。(位置實參和任意數量實參共用)
如果要讓函數接受不同類型的實參,必須在函數定義中將接納任意數量實參的形參放在最后。Python先匹配位置實參和關鍵字實參,再將余下的實參都收集到最后一個形參中。舉例:
def make_pizza(size,*toppings): # 帶*號的意思是創建一個名為toppings的空元組,將收到的所有值都封裝到這個元組內"""說明要制作的披薩"""print("\nMaking a " + str(size) + "-inch pizza with the following toppings:")for topping in toppings:print("- " + topping)make_pizza(16,'pepperoni')
make_pizza(12,'mushrooms','green peppers','extra cheese')
2、結合關鍵之字實參,使用任意數量實參。(關鍵字實參和任意數量實參共用)
這種方法適合于需要接受任意數量的實參、未知類型的實參。此時,可以將函數編寫成能夠接受任意數量的鍵—值對(調用語句提供多少就接受多少)
def build_profile(first,last,**user_info): # "**"代表字典,“*”代表列表# 函數build_profile()的定義要求提供姓和名,同時允許用戶根據需要提供任意數量的“名稱-值”對。# “**user_info”代表創建一個名為user_info的空字典,并將所有收到的“名稱—值”對,存入這個字典"""創建一個字典,其中包含我們知道的有關用戶的一切"""profile = {}profile['first_name'] = firstprofile['last_name'] = lastfor key, value in user_info.items():profile[key] = valuereturn profile
user_profile = build_profile('albert','einstein',location='princeton',field='physics')print(user_profile)
綜上,通過上述實驗,我們知道:在編寫函數時,可以以各種方式混合使用位置實參、關鍵字實參和任意數量的實參。
六、函數與模塊的關系
大家都知道“調包俠”的梗,其中的包就是我們接下來要說的模塊。而函數的優點之一時可以讓他們的代碼塊與主程序分離。因此,我們可以把函數存儲在被稱為“模塊”的獨立文件中,再將模塊使用“import”導入到主程序中。如此一來,不僅可以隱藏程序代碼的細節,而且還可以將重點放在程序的高層邏輯上,最后還可以提高程序的重用性。下面將會給出例代碼。
第一個文件是模塊文件,名為code,我自己起的。
def make_pizza(size,*toppings):"""概述要制作的披薩"""print("\nMaking a " + str(size) + "-inch pizza with the following toppings:")for topping in toppings:print("- " + topping)
第二個文件是主程序文件,名為making_pizzas,書上起的。
import code
# 要想使用被調入模塊的函數,得寫明是哪個模塊的哪個函數
# 可簡單的理解為“模塊名+‘.’+函數名”。
code.make_pizza(16,'pepperoni')
code.make_pizza(12,'mushrooms','green peppers','extra cheese')
運行時需注意,要運行第二個文件,不能直接點擊“run”,要在第二個文件代碼間,點擊右鍵,運行代碼。(Pycharm)整個程序運行的機理是:Python編譯器讀取到這個文件時,代碼運行import code,讓Python編譯器打開文件code.py,并將其中的所有函數都復制到這個程序中。你看不到這個復制過程,因為它在幕后進行。上述過程完成之后,code.py中的所有函數都能在making_pizza.py中使用。(調用模塊的程序和未調用模塊的原始程序的輸出結果是完全相同的)
1、導入模塊中的特定函數
如果模塊過大,而我們只需要導入其中的部分函數即可:“from? 模塊名 import 函數名”,如果還以做披薩的代碼為例,那么如下:
from code import make_pizzamake_pizza(16,'pepperoni')
make_pizza(12,'mushrooms','green peppers','extra cheese')
若使用這樣直接導入模塊函數的方法,在使用方法時,就無需指明是哪個模塊了,直接用就OK了。
2、使用as為函數、模塊重命名
使用模塊多了之后,模塊名或者函數名容易沖突(最好在命名時要遵守一定的規則,我們在第4部分介紹),有的函數名過長,在使用時不太方便,所以需要為這些“特殊情況”指定新的、簡短的、獨一無二的名字(nickname)。還以做披薩為例:
from code import make_pizza as mp
# 使用as,將起的nickname加在后面mp(16,'pepperoni')
mp(12,'mushrooms','green peppers','extra cheese')
第一句代碼執行之后,make_pizza()重命名為mp(),在以后需要使用make_pizza()函數時,都可簡寫程mp()。以上是函數的重命名,接下來是模塊的重命名,仍然以做披薩為例:
import code as p
# 用as將模塊名“code”改為“p”
# 下面調用make_pizza()的函數調用方法對應改變,如下所示!!!p.make_pizza(16,'pepperoni')
p.make_pizza(12,'mushrooms','green peppers','extra cheese')
這樣做的好處是:不僅能使代碼簡化,而且可以讓我們更專注于描述函數性的函數名,這些函數名指出了函數的功能。
3、使用*號導入模塊中的所有函數
from code import *
# 用as將模塊名“code”改為“p”
# 下面調用make_pizza()的函數調用方法對應改變make_pizza(16,'pepperoni')
make_pizza(12,'mushrooms','green peppers','extra cheese')
這個和直接導入整個模塊有區別嗎?import語句中的*號讓模塊中的全部函數都復制到了主程序文件中,因此可以直接使用函數名稱來調用每個函數,無需再用“.”。但是,在使用并非自己寫的大型模塊時,最好不要采用這種導入方法:如果模塊中有函數的名稱與自己的項目中使用的名稱相同,可能會導致想不到的結構。最佳的做法是:要么導入你需要使用的函數,要么導入整個模塊并使用句點表示法。這樣做能讓代碼更清晰,容易閱讀、理解。
4、函數編寫指南
A、應該給函數指定描述性名稱,且只在其中使用小寫字母和下劃線。這樣看到函數名稱就知道這個函數的功能。模塊命名時,也是同樣的道理。
B、每個函數都應包含簡要的闡述其功能的注釋,該注釋應緊跟在函數定義后面,并采用文檔字符串格式。只要知道函數的名稱、需要的實參以及返回值類型,就能在自己的程序中使用它。
C、給實參指定默認值時,等號兩邊不要有空格。同樣,對于函數調用中的關鍵字實參,也是同樣的道理。
D、建議代碼行的長度不要超過79字符,這樣只要編輯其窗口適中,就能看到整行代碼。如果形參很多,導致函數定義的長度超過了79字符,可在函數定義中輸入左括號后按回車,并在下一行按2次Tab鍵,從而將形參列表和只縮進一層的函數本體區分開來。
E、如果程序或模塊包含多個函數,可使用兩個空行將相鄰的函數分開,這樣將更容易知道前一個函數在什么地方結束,下一個函數從什么地方開始。
F、所有的import語句都應該放在文件開頭,除了在文件開頭有注釋。
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?