Python 基礎系列--函數

Python 基礎系列--函數

來自專欄 Python 學習之道

題圖:自己按下相機快門的獼猴。

在中學數學中我們知道 y=f(x) 代表著函數,x 是自變數,y 是函數 f(x) 的值,給定 x 可以計算出對應的 y。在程序設計中,函數的功能是一樣的,給定輸入,返回對應的輸結果,變數 x 不在限制為數字,可以為任意的數據類型,比如字元串,列表,字典,對象,或者自定義的對象等,同樣地返回值也可以任意的數據類型。函數的作用是對加工細節的一種封裝,對外提供統一的介面,使用者無需關心函數對內的細節,是最基本的一種代碼抽象方式。

函數不僅減少代碼行數,而且能節省內存,提高程序運行速度:當一個函數調用完畢時,退出程序堆棧,內存空間被回收,當新的函數被調用時,局部變數又可以重新使用相同的地址。當一塊數據被反覆讀寫,其數據會留在 CPU 的一級緩存中,訪問速度非常快,從而加快程序執行速度。

下面來說一說 Python 中的函數。

定義一個函數

Python 定義函數的規則: - 函數代碼塊以 def 關鍵詞開頭,後接函數標識符名稱和圓括弧 ()。 - 任何傳入參數和自變數必須放在圓括弧中間,圓括弧之間可以用於定義參數。 - 函數的第一行語句可以選擇性地使用文檔字元串—用於存放函數說明。 - 函數內容以冒號起始,並且縮進。 - return [表達式] 結束函數,選擇性地返回一個值給調用方。不帶表達式的return相當於返回 None。

使用 def 關鍵字,一般格式如下:

def 函數名(參數列表): 函數體

以簡單的數據計算函數為例,定義函數 fun(a,b,h) 來計算上底為 a,下底為b,高為 h 的梯形的面積:

>>> def fun(a,b,h): #def 定義函數fun,參數為a,b,h... s=(a+b)*h/2 #使用梯形的面積計算公式,注意此行前有4個空格... return s #返回面積,注意此行前有4個空格...>>> fun(3,4,5) #計算上底為3,下底為4,高為5的梯形的面積17.5

函數的目的是封裝,提高應用的模塊性,和代碼的重複利用率。將常用的處理過程寫成函數,在需要的時候調用它,可以屏蔽實現細節,減少代碼量,增加程序可讀性。假如下許多個梯形的面積需要計算,實例如下:

>>> for a,b,h in [(3,4,5),(7,5,9),(12,45,20),(12,14,8),(12,5,8)]: #計算5個梯形面積... print("上底{},下底{},高{}的梯形,面積為{}".format(a,b,h,fun(a,b,h))) #字元串格式化函數format...上底3,下底4,5的梯形,面積為17.5上底7,下底5,9的梯形,面積為54.0上底12,下底45,20的梯形,面積為570.0上底12,下底14,8的梯形,面積為104.0上底12,下底5,8的梯形,面積為68.0

普通函數

上例中的調用方法fun(3,4,5)並不直觀,為了增加可讀性,我們稍做調整,並增加函數的文檔說明,如下:

>>> def trapezoidal_area(upperLength,bottom,height):... """函數說明:輸入:長、寬、高... 返回該梯形的面積"""... return (upperLength+bottom)*height/2...>>> trapezoidal_area(3,4,5) # 按定義的順序對應 upperLength=3,bottom=4,height=517.5>>> trapezoidal_area(upperLength=3,bottom=4,height=5) #顯式的指定參數的值,位置可以變化17.5>>> trapezoidal_area(bottom=4,height=5,upperLength=3) #顯式的指定參數的值,位置可以變化17.5>>>

可以使用 help 函數查看該函數的文檔說明:

>>> help(trapezoidal_area)Help on function trapezoidal_area in module __main__:trapezoidal_area(upperLength, bottom, height) 函數說明:輸入:長、寬、高 返回該梯形的面積

參數帶默認值的函數

在調用此函數傳遞參數的時候使用參數關鍵字,這樣參數的位置可以任意放置而不影響運算結果,增加程序可讀性。假如待計算的梯形默認高度都為 5,可以定義帶默認值參數的函數

>>> def trapezoidal_area(upperLength,bottom,height=5):#定義默認值參數... return (upperLength+bottom)*height/2...>>> trapezoidal_area(upperLength=3,bottom=4)17.5>>> trapezoidal_area(3,4)17.5>>> trapezoidal_area(3,4,5)17.5>>> trapezoidal_area(3,4,10)35.0

注意:帶有默認值的參數必須位於不含默認值參數的後面

參數個數不固定的函數

你可能需要一個函數能處理比當初聲明時更多的參數,此時你可以定義不定長參數,語法如下:

def 函數名([固定參數列表,] *不固定參數名 ): "函數_文檔字元串" 函數體 return [expression]

加了星號 * 的參數會以元組(tuple)的形式導入,存放所有未命名的變數參數。 舉個例子:

#!/usr/bin/python3# 可寫函數說明def printinfo( arg1, *vartuple ): "列印任何傳入的參數" print ("輸出: ") print (arg1) for var in vartuple: print (var)# 調用printinfo 函數printinfo( 10 ) #不向函數傳遞未命名的變數printinfo( 70, 60, 50 ) #向函數傳遞未命名的變數

輸出結果為:

輸出: 10輸出: 706050

還有一種就是參數帶兩個星號 **的參數會以字典的形式傳入:

#!/usr/bin/python3# 可寫函數說明def printinfo( arg1, **vardict ): "列印任何傳入的參數" print ("輸出: ") print (arg1) print (vardict)# 調用printinfo 函數printinfo(1, a=2,b=3)

輸出結果為:

輸出: 1{a: 2, b: 3}

聲明函數時,參數中星號 * 可以單獨出現,例如:

def f(a,b,*,c): return a+b+c

如果單獨出現星號 * 後的參數必須用關鍵字傳入。

>>> def f(a,b,*,c):... return a+b+c... >>> f(1,2,3) # 報錯Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: f() takes 2 positional arguments but 3 were given>>> f(1,2,c=3) # 正常6>>>

是值傳遞還是引用傳遞?

關於函數是否會改變傳入變數的值分兩種情況: (1)對不可變數據類型的參數,函數無法改變其值,如字元串,數字,元組等。 (2)對可變數據類型的參數,函數可以改變其值,如列表,字典,集合等。 這裡什麼是可變數據類型,什麼是不可變數據類型,請參考上一篇文章 Python 的可變/不可變數據類型。

請嘗試說出下面程序的輸出結果:

# !/usr/local/bin/python3# -*- coding: utf-8 -*-# Time: 2018/10/6 7:36:38# Description:# File Name: lx_fun_params.pydef change_nothing(var): var = "new value"def try_change(var): if type(var) is list: var.append("new value") elif type(var) is str: var = var + " new value" else: passdef try_change1(var): var = var+"a"str1 = "old value"list1 = ["old value"]change_nothing(str1)change_nothing(list1)print("after call change_nothing:")print(str1)print(list1)#恢復原值str1 = "old value" list1 = ["old value"] try_change(str1) try_change(list1)print("after call try_change:")print(str1)print(list1)

按照 C/C++ 的思維會產生函數參數是值傳遞,還是引用傳遞。有些同學可會潛移默化的認為列表是屬於引用傳遞, change_nothing 調用之後 str1 未被改變,list1 變成字元串 「new value", try_change 調用之後 str1 未被改變,list1 會新加入元素 「new value"。 真正的結果是:

Python 函數參數的傳遞既不是所謂的傳值也不是傳引用。如果你理解發什麼是可變數據類型 ,什麼是不可變數據類型,這就很好理解。請牢記,在 Python 世界裡,萬物皆對象,變數是對象的引用,代表著對象在內存中的地址。Python 中函數參數傳遞的是變數的值,即就是變數所指向的對象的地址。 對上例中的字元串 str1 ,如下圖所示:在調用 change_nothing 傳入參數時前,str1 與 var 均指向 "old value" 的地址,調用 change_nothing 後,var 指向了新的對象 "new value",因此 str1 未發生任何變化,對字元串 str1 調用 try_change 的本質與 change_nothing 是一樣的,同樣都是賦值操作,因此 str1 均不發生變化。

list1 也是同樣的道理,因此在調用 change_nothing 之後,list1 的值仍然是 ["old value"]

但是在調用 try_change 函數時,發生了變化。如下圖所示

開始傳參時 list1 和 var 均指向 ["old value"],由於列表是可變數據類型,增加、刪除、修改元素時不產生新的對象,對象在內存中的地址不發生變化,var 仍指向原來的 list1 的地址,因此在調用 try_change 函數後,list1 被改變。

涉及到的其他小知識:

(1)isinstance 和 type 的用法: python 判斷一個變數屬於什麼對象可以使用 isinstance 和 type,二者的區別在於判斷有繼承關係的類時 isinstance 認為子類是父類,type 則認為子類不是父類,如下所示:

class A: passclass B(A): # B 是 A 的子類 passisinstance(A(), A) # returns Truetype(A()) == A # returns Trueisinstance(B(), A) # returns Truetype(B()) == A # returns False

(2)匿名函數: python 使用 lambda 來創建匿名函數。 所謂匿名,意即不再使用 def 語句這樣標準的形式定義一個函數。 語法 lambda 函數的語法只包含一個語句,如下:

lambda [arg1 [,arg2,.....argn]]:expression

例子:

#!/usr/bin/python3# 可寫函數說明sum = lambda arg1, arg2: arg1 + arg2# 調用sum函數print ("相加後的值為 : ", sum( 10, 20 ))print ("相加後的值為 : ", sum( 20, 20 ))

以上實例輸出結果:

相加後的值為 : 30相加後的值為 : 40

(完)

文章首發公眾號,如果覺得這篇文章對您有幫助,請關注公眾號 somenzz 獲取最新消息或推薦給需要的朋友。

weixin.qq.com/r/6igJEUz (二維碼自動識別)


推薦閱讀:

TAG:Python | 科技 | 函數 |