函數是帶有名字的程式碼區塊,用來完成特定的工作。 def函數定義,指出函數名稱。定義函數的時候,我們把參數的名字和位置確定下來,函數的介面定義就完成了。對於函數的呼叫者來說,只需要知道如何傳遞正確的參數,以及函數將傳回什麼樣的值就夠了,函數內部的複雜邏輯被封裝起來,調用者無需了解。
要執行函數定義的特定任務,可呼叫函數。在程式中多次執行同一項任務時,無需重複編寫完成該任務的程式碼,只需呼叫執行該任務的函數,讓Python運行其中的程式碼。
高階函數英文叫Higher-order function
形參:函數完成其工作所需的一項資訊。
實參:呼叫函數時專遞給函數的資訊。
函數定義中可能包含多個形參,因此函數呼叫時也可能包含多個實參。傳遞實參給函數的方式很多,可使用位置實參,這要求實參的順序與形參的順序相同;也可使用關鍵字實參字,其中每個實參都由變數名和值組成;也可使用列表和字典。
1.當你呼叫函數時,Python必須將函數呼叫中的每個實參都關聯到函數定義中的一個形參。簡單的關聯方式是基於實參的順序。這種關聯方式稱為位置實參。
2.在函數中,可依需求使用任意數量的位置實參,Python將依序將函數呼叫中的實參關聯到函數定義中對應的形參。
3.使用位置實參來呼叫函數時,如果實參的順序不正確,輸出也不會正確。
關鍵字實參是傳遞給函數的(名稱—值)對。你直接在實參中將名稱和值關聯起來了,因此向函數傳遞實參時不會混淆。關鍵字實參讓你無需考慮函數呼叫中的實參順序,還清楚指出了函數呼叫中各個值的用途。
describe_pet(animal_type='hamster', pet_name='harry') |
可以傳入任數的關鍵字參數:
>>> person(' Bob', 35, city='Beijing') name: Bob age: 35 other: {'city': 'Beijing'} >>> person('Adam ', 45, gender='M', job='Engineer') name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'} |
寫函數時,可給每個形參指定預設值。在呼叫函數中給形參提供了實參時,Python將使用指定的實參值;否則,將使用形參的預設值。因此,給形參指定預設值後,可在函數呼叫中省略對應的實參。使用預設值可簡化函數調用,也可清楚指出函數的典型用法。最大的好處是能降低呼叫函數的難度。
預設參數必須指向不變物件!
def add_end(L=None): if L is None: # L = [] L.append('END') return L |
定義可變參數和定義一個list或tuple參數相比,僅在參數前面加了一個*號。在函數內部,參數numbers接收的是一個tuple,因此,函數程式碼完全不變。但是,當呼叫函數時,可以傳入任意個參數,包括0個參數。
def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum |
o> |
*nums表示把nums這個list的所有元素當作可變參數傳進去。
對於關鍵字參數,函數的呼叫者可以傳入任意不受限制的關鍵字參數。至於到底傳入了哪些,就需要在函數內部通過kw檢查。
如果要限制關鍵字參數的名字,就可以用命名關鍵字參數,例如,只接收city和job作為關鍵字參數。此方式定義的函數如下:
def person(name, age, *, city, job): print(name, age, city, job) |
和關鍵字參數**kw不同,命名關鍵字參數需要一個特殊分隔符號*,*後面的參數被視為命名關鍵字參數。
如果函數定義中已經有了一個可變參數,後面跟著的命名關鍵字參數就不再需要一個特殊分隔符號*了:
def person(name, age, *args, city, job): print(name, age, args, city, job) |
# 命名關鍵字參數必須傳入參數名,這和位置參數不同。如果沒有傳入參數名,呼叫會錯誤:
>>> person('Jack', 24, 'Beijing', 'Engineer' ) Traceback (most recent call last): File " TypeError: person() takes 2 positional arguments but 4 were given |
# 由於呼叫時缺少參數名稱city和job,Python解釋器把這4個參數都視為位置參數,但person ()函數只接受2個位置參數。
命名關鍵字參數可以有預設值(預設值),從而簡化呼叫:
def person(name, age, * , city='Beijing', job): print(name, age, city, job) |
# 由於命名關鍵字參數city具有預設值,當呼叫時,可不傳入city參數:
>>> person('Jack', 24, job='Engineer' ) #Jack 24 Beijing Engineer |
# 定義命名的關鍵字參數在沒有可變參數的情況下不要忘了寫分隔符號* ,否則定義的將是位置參數。
輸出結果相同,呼叫方法不同。
注意:使用哪一種呼叫方式無關緊要,只要函數呼叫能產生你希望的輸出就行。使用對你來說最容易理解的呼叫方式即可。
如果想定義一個什麼事也不做的空函數,可以用pass語句:
def nop(): pass |
# pass語句什麼都不做,pass可以用來當作佔位符,例如現在還沒想好怎麼寫函數的程式碼,就可以先放一個pass,讓程式碼能運作起來。
沒有給定實參,實參順序錯誤,沒有註意實參格式(引號等)。
如果想要在一個函數中修改全域變數中儲存的值,就必須對該變數使用 global 語句。
函數並非總是直接顯示輸出,相反,它可以處理一些數據,並傳回一個或一組值。函數傳回的值稱為返回值。函數的回傳值用return語句傳回。
在函數中,可使用return 語句將值傳回呼叫函數的程式碼行。傳回值讓你能夠將程式的大部分繁重工作移到函數中去完成,從而簡化主程式。
import math語句表示導入math包,並允許後續程式碼引用math包裡的sin、cos等函數。就可以傳回多個值了。
>>> x, y = move(100, 100, 60, math.pi / 6) > >> print(x, y) 151.96152422706632 70.0 |
但其實這只是一種假象,Python函數回傳的仍然是
# 但其實這只是一種假象,Python函數回傳的仍然是單一值:>>> r = move(100, 100, 60, math.pi / 6)> ;>> print(r)(151.96152422706632, 70.0) |
回傳值是一個tuple!但是,在語法上,回傳一個tuple可以省略括號,而多個變數可以同時接收一個tuple,依位置賦給對應的值,所以,Python的函數回傳多值其實就是回傳一個tuple,但寫起來比較方便。
透過if語句,來判斷是否需要這個實參。
函數可傳回任何類型的值,包括清單和字典等較複雜的資料結構。
高階函數除了可以接受函數當作參數外,還可以把函數傳回作為結果值。如果不需要立刻求和,可以不回傳求和的結果,而是傳回求和的函數:
#def lazy_sum(*args): def sum(): ax = 0 for n in oo return ax return sum | # 當我們呼叫lazy_sum()時,傳回的並不是求和結果,而是求和函數:
| 呼叫函數f時,才真正計算求和的結果:
>>> f1==f2 False
| #
8.3.5結合使用函數和while
full_name = first_name + ' ' + last_name return full_name.title() ##while True print("\nPlease tell me your name:") print("(enter 'q' at any time to quit)") f_name = input("First name: " if f_name == 'q': break##o== : break formatted_name = get_formatted_name(f_name, l_name) ##print("#nname#"", l_name),##print("#n" print("#nname"#8.3.6閉包 返回閉包時牢記的一點就是:返回函數不要引用任何循環變量,或者後續會發生變化的變量。如果一定要引用循環變量,方法是再創建一個函數,用該函數的參數綁定循環變量當前的值,無論該循環變量後續如何更改,已綁定到函數參數的值不變,缺點是代碼較長,可利用lambda函數縮短程式碼。 | def count():
# def g():
# fs = [] # i)) # f(i)立刻被執行,因此i的目前值被傳入f()return fs>>> f1, f2, f3 = count ()>>> f1()1>>> f2()4#>>> f3()9# 8.3.7匿名函數lambda當我們在傳入函數時,有些時候,不需要明確地定義函數,直接傳入匿名函數比較方便。 在Python中,對匿名函數提供了有限支援。還是以map()函數為例,計算f(x)=x2時,除了定義一個f(x)的函數外,還可以直接傳入匿名函數:
透過對比可以看出,匿名函數函數(lambda x: x * xlambda實際上就是:
8.4傳遞清單for name in names:你常常會發現,傳遞清單清單很有用,而這個清單包含的可能是名字、數字或更複雜的對象(如字典)。將列表傳遞給函數後,函數就能直接存取其內容。以下使用函數來提高處理清單的效率。 8.4.1在函數中修改列表1.將列表傳遞給函數後,函數就可對其進行修改。在函數中對這個列表所做的任何修改都是永久性的,這讓你能夠有效率地處理大量的資料。 2.增加或刪除 append pop # 3.一個函數完成一個任務,函數可以互相呼叫。優化函數,方便維護和修改。 8.4.2禁止修改函數為解決這個問題,可向函數傳遞清單的副本而不是原件;這樣函數所做的任何修改都只影響副本,而絲毫不影響原件。 function_name(list_name[:]),[:]相當於複製。 8.5傳遞任意數量的實參(可變參數)有時候,你預先不知道函數需要接受多少個實參,好在Python允許函數從呼叫語句中收集任意數量的實參。 def make_pizza(*toppings): # 形參名*toppings 中的星號讓Python建立一個名為toppings 的空元組,並將收到的所有值都封裝到這個元組中。函數體內的print 語句透過產生輸出來證明Python能夠處理 使用一個值呼叫函數的情形,也能處理使用三個值來呼叫函數的情形。它以類似的方式處理不同的調用,注意,Python將實參封裝到一個元組中,即便函數只收到一個值也是如此。 8.5.1結合使用位置實參和可變實參如果要讓函數接受不同類型的實參,必須在函數定義中將接納任意數量實參的形參放在最後。 Python先匹配位置實參和關鍵字實參,再將餘下的實參都收集到最後一個形參中。 8.5.2使用可變實參與關鍵字實參def build_profile(first, last, **user_info): # 函數build_profile() 的定義要求提供名和姓,同時允許使用者根據需要提供任意數量的名稱—值對。形參**user_info 中的兩個星號讓Python建立一個名為user_info 的 空字典,並將收到的所有名稱—值對都封裝到這個字典中。在這個函數中,可以像存取其他字典一樣存取user_info 中的名稱—值對。 for key, value in user_info.items(): profile[key] = value user_profile = build_profile('albert', 'profile('albert', 'profile('albert', 'profile('albert)' princeton', field='physics') 8.5.3使用參數組合1.在Python中定義函數,可以用必選參數、預設參數、可變參數、關鍵字參數和命名關鍵字參數,這5種參數都可以組合使用。但請注意,參數定義的順序必須是:必選參數、預設參數、可變參數、命名關鍵字參數和關鍵字參數。 2.python的函數具有非常靈活的參數形態,既可以實現簡單的調用,又可以傳入非常複雜的參數。 3.預設參數一定要用不可變對象,如果是可變對象,程式運作時會有邏輯錯誤! 4.要注意定義可變參數和關鍵字參數的語法: 5.*args是可變參數,args接收的是一個tuple; 6 .**kw是關鍵字參數,kw接收的是一個dict。 7.以及呼叫函數時如何傳入可變參數和關鍵字參數的語法: 可變參數既可以直接傳入:func(1, 2, 3),又可以先組裝list或tuple,再透過*args傳入:func(*(1, 2, 3)); 關鍵字參數既可以直接傳入:func(a=1, b=2 ),又可以先組裝dict,再經由**kw傳入:func(**{'a': 1, 'b': 2})。 使用*args和**kw是Python的習慣寫法,當然也可以用其他參數名,但最好使用習慣用法。 8.5.4遞迴參數在函數內部,可以呼叫其他函數。如果一個函數在內部呼叫自身本身,這個函數就是一個遞歸函數。遞歸函數的優點是定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環的方式,但循環的邏輯不如遞歸清晰。
使用遞歸函數需要注意防止堆疊溢位。在電腦中,函數調用是透過棧(stack)這種資料結構實現的,每當進入函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸呼叫的次數過多,會導致棧溢位。 漢諾塔的移動可以用遞歸函數非常簡單地實現。 8.6高階函數8.6.1高階函數既然變數可以指向函數,函數的參數能接收變量,那麼一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數。
# 8.6.2內建函數# 1.求絕對值的函數abs(),只有一個參數。 2.判斷物件類型,使用type()函數:
3.計算平方根可以呼叫math .sqrt()函數。 4.lower()傳回小寫的字串。 5.__len__方法回傳長度。在Python中,如果你呼叫len()函數試圖取得一個物件的長度,實際上,在len()函數內部,它會自動去呼叫該物件的__len__()方法,所以,下面的程式碼是等價的:
# 6.max函數max()可以接收任意多個參數,並傳回最大的那個。 7.如果要取得一個物件的所有屬性和方法,可以使用dir()函數,它傳回一個包含字串的list,例如,取得一個str物件的所有屬性和方法: 8.Python內建的hex()函數把一個整數轉換成十六進位表示的字串。 9.對於class的繼承關係來說,使用type()就很不方便。我們要判斷class的類型,可以使用isinstance()函數。參數類型做檢查,只允許整數和浮點數類型的參數。資料類型檢查可以用內建函數isinstance()實作。使用內建的isinstance函數可以判斷一個變數是不是字串。 10.配合getattr()、setattr()以及hasattr(),我們可以直接操作一個物件的狀態。
# 透過內建的一系列函數,我們可以對任一個Python對象進行剖析,拿到其內部的數據。要注意的是,只有在不知道物件資訊的時候,我們才會去取得物件資訊。 11.Python內建了map()和reduce()函數。 map將傳入的函數依序作用到序列的每個元素,並將結果傳回為新的Iterator。
reduce的用法。 reduce把一個函數作用在一個序列[x1, x2, x3, ...]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素做累積計算。
12.Python內建的filter()函數用來過濾序列。 filter()也接收一個函數和一個序列。和map()不同的是,filter()把傳入的函數依序作用於每個元素,然後根據回傳值是True還是False決定保留還是丟棄該元素。關鍵在於正確實作一個「篩選」函數。注意到filter()函數回傳的是一個Iterator,也就是一個惰性序列,所以要強迫filter()完成計算結果,需要用list()函數得到所有結果並回傳list。
# 13.Python內建的sorted()函數就可以對list進行排序。
# 14.sorted()函數也是高階函數,它也可以接收一個key函數來實作自訂的排序,例如按絕對值大小排序。要進行反向排序,不必改變key函數,可以傳入第三個參數reverse=True。
15.Python內建的enumerate函式可以把一個list變成索引-元素對,這樣就可以在for迴圈中同時迭代索引和元素本身。 16.round()函數可以傳回任意位元的小數。 8.6.3裝飾器在程式碼運作期間動態增加功能的方式,稱為「裝飾器」(Decorator)。本質上,decorator就是一個返回函數的高階函數。裝飾器:不改變函數本身,增加新的功能。 函數物件有一個__name__屬性,可以拿到函數的名字:
# Python內建的@property裝飾器就是負責把一個方法變成屬性呼叫的:
@property廣泛地應用在類別的定義中,可以讓呼叫者寫出簡短的程式碼,同時保證對參數進行必要的檢查,這樣,程式運行時就減少了出錯的可能性。 @unique裝飾器可以幫助我們檢查保證沒有重複值。 8.7將函數儲存在模組中為了寫可維護的程式碼,可以把很多函數分組,分別放到不同的檔案裡,這樣,每個檔案包含的程式碼相對較少。在Python中,一個.py檔案就稱為一個模組(Module)。 函數的優點之一是,使用它們可將程式碼區塊與主程式分開。透過給函數指定描述性名稱,可讓主程式容易理解得多。你也可以更進一步,將函數儲存在被稱為模組的獨立檔案中, 再將模組匯入到主程式中。 import 語句允許在目前執行的程式檔案中使用模組中的程式碼。 透過將函數儲存在獨立的檔案中,可隱藏程式碼的細節,並將重點放在程式的高層邏輯上。這也能讓你在眾多不同的程式中重複使用函數。將函數儲存在獨立檔案中後,可與其他程式設計師共用這些檔案而不是整個程式。知道如何導入函數也能讓你使用其他程式設計師編寫的函數庫。 為了避免模組名稱衝突,Python又引入了按目錄來組織模組的方法,稱為套件(Package)。引入了包以後,只要頂層的包名不與別人衝突,那麼所有模組都不會與別人衝突。現在,abc.py模組的名字變成了mycompany.abc,類似的,xyz.py的模組名稱變成了mycompany.xyz。 注意,每一個套件目錄下面都會有一個__init__.py的文件,這個文件是必須存在的,否則,Python就把這個目錄當成普通目錄,而不是一個包。 __init__.py可以是空文件,也可以有Python程式碼,因為__init__.py本身就是一個模組,而它的模組名稱就是mycompany。 8.7.1導入整個模組要讓函數是可導入的,得先建立模組。模組是擴展名為.py的文件,包含要導入到程式中的程式碼。
使用模組的第一步就是導入模組,當我們在命令列運行hello模組檔案時,Python解釋器把一個特殊變數__name__置為__main__,如果在其他地方導入該hello模組時, if判斷將會失敗,因此,這種if測試可以讓一個模組透過命令列運行時執行一些額外的程式碼,最常見的就是執行測試。 8.7.2作用域在一個模組中,我們可能會定義很多函數和變量,但有的函數和變數我們希望給別人使用,有的函數和變數我們希望僅僅在模組內部使用。在Python中,是透過_前綴來實現的。正常的函數和變數名稱是公開的(public),可以被直接引用,例如:abc,x123,PI等;類似__xxx__這樣的變數是特殊變量,可以直接引用,但是有特殊用途,例如上面的__author__,__name__就是特殊變量,hello模組定義的文檔註釋也可以用特殊變量__doc__訪問,我們自己的變量一般不要用這種變量名;類似_xxx和__xxx這樣的函數或變量就是非公開的(private),不應該被直接引用,例如_abc,__abc等; private函數或變數不應該被別人引用,那它們有什麼用呢?請看範例:
|
以上是Python基礎入門--函數的詳細內容。更多資訊請關注PHP中文網其他相關文章!