本文實例講述了python定義函數功能與用法。分享給大家供大家參考,具體如下:
1.函數的意義
一般數學上的函數是,一個或者幾個自變量,通過某種計算方式,得出一個因變量。
y = f(x)
在python中,為了使操作更加簡潔,就引入了函數這個概念。
python中的函數,可以把一大串要反復使用的代碼“定義”(封裝)成一個函數,給予這個函數一個標識符作為函數名,設置自變量和因變量。然后要使用這一大串代碼的時候,就調用這個我們自己創造的函數,輸入自變量,然后會返回給我們因變量。
2.函數的定義
在python中,對應數學函數的自變量和因變量的值,叫做參數和返回值。
用def來定義一個函數,下面是一個通用的模型,函數都這么定義:
1
2
3
4
5
|
def 函數名(參數列表): ... 代碼塊 ... return 返回值 |
然后舉一個例子,假設我們要判定一個學生的成績是及格還是不及格:
1
2
3
4
5
6
7
8
9
|
def judge_score(score): judge = none if score > = 60 : judge = true print ( 'pass' ) else : #雖然成績應該在0-100之間,但是我懶,就不限定范圍了。 judge = false print ( 'fail' ) return judge |
然后試一下:
1
2
3
4
5
6
|
judge_score( 59 ) fail j = judge_score( 61 ) pass print (j) true #說明j接收到的值為true |
這段代碼很簡單,但是也不能每次要判斷一次成績,就敲出這么一大串,所以為了便捷,就給這段代碼用def(define,定義)封裝成一個函數。
給一個函數名judge_score,后面調用就可以用judge_score()
。
score是我們要輸入的自變量,也就是要在這個judge_score()
中進行運算的值。
接下來要注意:
這個'pass'和'fail' 不是函數的返回值!
這個'pass'和'fail' 不是函數的返回值!
這個'pass'和'fail' 不是函數的返回值!
‘pass'和'fail' 是函數內部的print()打印出來的內容,函數的返回值是寫在return后面的內容。
return后面可以做運算,也可以直接寫變量。如果這個函數的語句塊內沒有寫return語句,那么說明沒有定義返回值,也就是說調用函數什么都沒有返回,如果拿一個標識符來接受這個函數的返回值,只能接受到none。
return后面的返回值可以是多個,多個的話,就用,隔開,然后封裝成一個元組再返回。
函數名也是標識符,有的時候可以用callable()來判斷這個標識符是不是一個可以調用的。
1
|
callable (judge_score) #返回值為true |
注意別再檢查的函數名標識符后面加()。
3.函數的參數
在定義函數時,函數名后面的()里面叫做參數列表,這個參數列表里的參數,都是要在下面的代碼塊中要使用的。
3.1 形式參數和實際參數
形式參數,就是指在定義函數時,參數列表里寫出的參數,他們都還只是標識符,都會在下面的代碼中出現。此時此刻他們沒有具體的值,只是一個“外殼”,所以叫形式參數。
比如y = 3x 里面這個x,只是一個形式,沒有具體的值。
實際參數,是指在調用參數時,函數名后面的括號內給出具體的值,每個值都會與某一個形式參數對應。這些值是存在的,不止有個外殼。也可以算作是數學函數中的自變量,用它們來計算出因變量。比
如y = 3x ,假設x=3,這個3就是實際參數。
注意,每一個形式參數都必須獲得一個值(如果沒有默認值),才能進行計算,否則會報錯!
3.2 傳遞參數的類型和參數的類型
3.2.1 傳遞參數類型
傳遞參數,我的理解就是把實際參數和形式參數連接起來。
定義一個函數bmi,用來計算人的body mass index:
1
2
3
|
def bmi(height, weight): index = "%.2f" % (weight / (height * * 2 )) #留兩位小數 return index |
位置傳參
調用它:
1
|
bmi( 1.71 , 65 ) #返回值為22.23 |
可以看到我們的形式參數有height和weight,實際參數是1.71和65。在調用函數時,這兩個實際參數沒有指定誰是height,誰是weight,是按定義函數時,參數列表里面的形式參數的位置一一對應起來。這樣就叫做位置傳參。
關鍵詞傳參
調用它:
1
|
bmi(weight = 65 , height = 1.71 ) #返回值22.23 |
如果函數的調用者知道形式參數的標識符,也可以直接用 形式參數名=值 這樣的方式來傳遞參數,可以理解為賦值。因為是用關鍵字傳遞參數,所以位置可以和定義函數時參數列表內的形式參數順序不同。
混合傳參
有的時候,我們可能有一些參數要用位置傳參,有一些要用關鍵字傳參。萬幸的是他們可以混合使用。但是要遵守一定的規則:
1
|
bmi( 1.71 , weight = 65 ) #返回值為22.23 |
前面的1.71用的是位置傳參,后面65用的是關鍵詞傳參。
注意,關鍵字傳參一定要寫在位置傳參之后!不然就會報錯。
3.2.2 參數類型
位置參數的可變參數
比如有這樣一個需求,要輸入若干個數字,然后求出這若干個數字中的最大值和最小值。分析一下,若干個數字,也就是不知道數字的個數,這樣也就不知道設置多少個形式參數。這樣就可以用,*args,可變參數。
如果在定義函數時*args的左側有參數,那么在調用時,實際參數依次給予*arg左邊的用位置傳參的普通形式參數之后,剩下的實際參數無論多少都會被*args接收,和切片有一定的相似之處,但是差別也不小。
舉個例子
1
2
3
4
5
6
7
8
9
10
|
def maxmin(x, * nums): #這個x是為了顯示*args的收取范圍 length = len (nums) for i in range ( 1 ,length): #這是選擇排序的思路,走一趟,練練手 maxindex = 0 minindex = 0 if nums[i] > nums[maxindex]: maxindex = i if nums[i] < nums[minindex]: minindex = i print ( 'x={};{};max:{};min:{}' . format (x,nums,nums[maxindex],nums[minindex])) |
調用這個函數:
1
2
|
maxmin( 100 , 12 , 23 , 34 , 45 , 67 , 9 ) x = 100 ;( 12 , 23 , 34 , 45 , 67 , 9 ); max : 12 ; min : 9 |
可以看出,在給出的實際參數中,除了第一個100,被x接收了,剩下的實際參數都被*nums接收了,然后封裝成了一個元組(講道理,用逗號隔開了又被一個標識符接收,確實應該封裝成元組)。
注意,和切片不同的是,定義函數時*args會無限接收實際參數,不會給后面的形式參數留值,所以在定義函數時的參數列表里,位置參數的可變參數一定要在普通位置參數之后!
關鍵字參數的可變參數
關鍵字參數的可變參數,不是收集多個用關鍵字傳參的實際參數,而是收集關鍵字傳參的關鍵字和值,并把他們當做一個鍵值對,收集在一個字典內,在代碼塊中使用。
1
2
3
4
|
def test(x = 1 , * * nums): print (x,nums) test(c = 3 ,x = 2 ,a = 1 ,b = 2 ) 2 { 'c' : 3 , 'a' : 1 , 'b' : 2 } |
可以看出,用關鍵字傳參,可以不按位置順序來,先把
關鍵字參數的可變參數,和位置參數的可變參數一樣,會無限接收實際參數,不過是接收實際參數的關鍵字和值,組成鍵值對。
3.3 默認參數
有的時候,一些參數變化不頻繁,以上面定義的這個bmi函數來看,假設一個班里面的99%的同學,身高變化很大,體重都是75kg,每次用這個函數算同學的bmi,都要重新輸入一遍身高體重,比較麻煩,所以可以在定義函數時,給形式參數設置一個默認值:
1
2
3
|
def bmi(height = 1.80 , weight = 75 ): index = "%.2f" % (weight / (height * * 2 )) #留兩位小數 return index |
注意,這是在定義函數時,就設置好默認值,然后調用:
1
2
3
4
5
6
|
bmi( 1.80 ) #此時按height=1.80,weight=75來計算 bmi( 1.71 ) #此時按height=1.71,weight=75來計算 bmi( 65 ) #此時按height=65,weight=75來計算,注意和上面的區別 bmi( 1.71 , 65 ) #此時按height=1.71,weight=65來計算 bmi(height = 1.90 ) #此時按height=1.90,weight=75來計算 bmi( 1.80 , weight = 80 ) #此時按height=1.80,weight=80來計算 |
也就是說,如果沒有傳參,那么形式參數就會按定義參數時設置的默認值來計算。
如果用位置傳參,就按形式參數的位置順序,依次往后覆蓋,如果實際參數沒給夠,那么沒有接收到位置傳參的形式參數就會用默認值。(也就是說,在定義函數時,雖然設置了默認值,看起來和關鍵字參數似的,但是它也是有前后位置順序的。)
如果用關鍵字傳參,理解就比較簡單了,如果給了,就用關鍵字傳參給的實際參數,如果沒給,就用默認值。
總之,如果普通的形式參數沒有設置默認值,就必須要接收一個實際參數來使用。如果形式參數在定義時設置了默認值,調用函數時,沒有給出實際參數,就用默認值,如果給了一個實際參數,就用這個新的實際參數把默認值覆蓋。
3.4 keyword-only參數
這個參數可以理解為,必須用關鍵字傳參才能獲得的參數。
這種參數為了區別于普通的形式參數,位置有所改變,在定義參數時,放在*args(位置參數的可變參數)和**kwargs(關鍵字參數的可變參數)之間,就代表這個參數是keyword-only參數。
1
2
|
def test( * words, x): print (words, x) |
調用這個函數:
1
2
3
4
5
6
|
test( 1 , 2 , 3 , 4 ) #這樣會報錯,因為這四個都是位置傳參,都被*word截獲了,x沒有值 test( 1 , 2 , 3 , x = 4 ) #這個沒毛病 ( 1 , 2 , 3 ) 4 test(x = 4 ) #這個也沒毛病,*word一個值都有截獲 () 4 |
注意,在定義函數時,參數列表里,keyword-only參數不能放在關鍵字參數的可變參數之后,因為keyword-only參數要用關鍵字傳參,**kwargs要截獲所有的關鍵字傳參的實際參數,keyword-only參數永遠也收不到值。
keyword-only參數還有另一種定義形式,比如,我們想要這個函數都用關鍵字傳參,并且不想接收任何位置傳參的實際參數(就是一用位置傳參就報錯),可以用一下這種形式:
1
2
|
def test( * , x, y): print (x,y) |
調用它:
1
2
3
|
test( 1 , x = 100 , y = 99 ) #會報錯,因為1沒有形參來接收它 test(x = 100 , y = 99 ) #沒毛病 100 99 |
3.6 參數列表的順序***
重中之重:
定義函數時:(位置參數,帶缺省值的位置參數,位置參數的可變參數,keyword-only參數,關鍵字參數的可變參數)
調用函數時:(用位置傳參的實際參數,用關鍵字傳參的實際參數)
但是一定要確保,定義函數時,參數列表里的每一個參數(可變參數除外,因為可變參數可以收集到0個實際參數)都要有值可以使用。
例:
1
2
3
4
5
|
def test(a,b,c,d = 5 , * nums,x = 'x' ,y = 'y' , * * ddict): print (a,b,c,d) print (nums) print (x,y) print (ddict) |
調用它:
1
2
3
4
5
|
test( 97 , 98 , 99 , 100 , 101 , 102 ,x = 'xx' ,y = 'yy' ,name = 'tom' ,age = 10 ) 97 98 99 100 ( 101 , 102 ) xx yy { 'name' : 'tom' , 'age' : 10 } |
可以看到,有六個用位置傳參的實際參數,雖然d有默認值,但是按位置來看,d也會接收到一個新的值100,因此nums只截獲了兩個位置參數。
然后是keyword-only參數,x和y,都得用關鍵字傳參。如果x和y不指定的話,就會使用默認值x='x',y='y'。
然后是關鍵字參數的可變參數,不能說是截獲,得說keyword-only參數只給它剩下了兩個,然后他們組成了一個字典
還要注意,在傳參的時候,位置傳參要放在最前面。然后是關鍵字傳參,從關鍵字傳參中把keyword-only參數挑走,然后剩下的給**kwargs。
3.5 實際參數的解構
有時候,定義參數的時候,有很多形參。但是實際參數,都存在一個list或者說別的數據結構中,一個個拿出來很麻煩,所以可以種參數解構來完成:
1
2
3
4
5
6
7
8
9
10
|
def maxmin(x, * nums): length = len (nums) for i in range ( 1 ,length): maxindex = 0 minindex = 0 if nums[i] > nums[maxindex]: maxindex = i if nums[i] < nums[minindex]: minindex = i print ( 'x={};{};max:{};min:{}' . format (x,nums,nums[maxindex],nums[minindex])) |
這個函數,是找若干個數中的最大值還有最小值的,它可以接受若干個值(因為定義函數時用的是*args)。但是如果給我們的是一個列表,那還真把每個元素都提取出來,輸入一遍嗎,太蠢了,可以直接在調用函數的時候解構。
用*解構:
1
2
3
|
lst = [ 100 , 12 , 23 , 34 , 45 , 67 , 9 ] maxmin( * lst) 100 9 |
再看一個例子,用**解構:
1
2
3
4
5
6
|
def test( * nums): print (nums) test({ 'a' : 1 , 'b' : 2 , 'c' : 3 }) ({ 'a' : 1 , 'b' : 2 , 'c' : 3 },) #打印出來一個元組,說明傳入的字典沒有解構 test( * { 'a' : 1 , 'b' : 2 , 'c' : 3 }) ( 'a' , 'b' , 'c' ) #打印的是key,字典解構是key的集合,沒毛病 |
1
2
3
4
|
def test(a,b,c): #因為要接字典解構出來的 print (a,b,c) test( * * { 'a' : 1 , 'b' : 2 , 'c' : 3 }) #**這樣是把字典里的鍵值對解構出來 1 2 3 |
4. 函數的返回值
在一些已經知道函數中,有的有返回值,比如input()
、sorted()
等,它們都可以用一個標識符來接收,形成一個變量。有的沒有返回值,比如print()
、list.append()
等方法,都是沒有返回值的。
在自定義函數中,也能做到這一點,用的是return語句:
1
2
3
4
5
6
7
|
def test(x = 5 ): a = x * * 2 print (a) b = test() 25 #這個25是print(a)的效果 b none |
這說明,我們定義的函數test()
,它是沒有返回值的,雖然出現了結果,但那時print()
語句的打印輸出,用標識符來接收test()
的輸出,什么都接收不到(什么都沒有就是none)。
如果用return語句:
1
2
3
4
5
6
7
8
|
def test(x = 5 ): a = x * * 2 print (a) return a b = test() 25 #這個還是print(a)的打印輸出 b 25 #這個是b剛剛接收函數的返回值 |
也就是說return語句后面的值,可以作為這個函數的返回值,這個值可以是一個變量,也可以是一個表達式,還可以寫多個用逗號隔開的值,不過最后會被封裝成一個元組返回。
1
2
3
4
|
def test(): .... return 1 , 2 , 3 , 4 #返回值其實是(1, 2, 3, 4) a, b, c, d = test() #這里用解構來接收返回值,會比較方便 |
如果寫了return語句,return后面的值會作為返回值輸出,但是如果不寫return語句,或者只寫了一個return,后面沒有值,就說明這個函數沒有返回值(其實是隱式調用了return = none)
自定義函數中,可以有多條return語句,在定義時候不會報錯,但是這些return語句只有一條會被執行,執行完這個return語句,函數就結束,其不會理會其他的return語句。
1
2
3
4
5
6
7
8
|
def comparethree(n): if n > 3 : return 3 else : print ( 'less than three' ) return false print ( 'nothing' ) return 'nothing' |
看著個自定義函數,如果實參大于3,返回值就是3,如果小于等于3,返回值就是false。
因為他用的是分支語句,只會進入其中一個分支,所以只有一個return會被執行。這個分支結構完成后,還有一個print()和return,這兩個是不會被執行的,因為分支語句中都有return,怎么著都會執行其中的一個,執行完就結束這個函數,所以后面的'nothing'和返回值字符串'nothing'都不會執行。
所以還能看出,return語句不一定是定義函數時寫的最后一條語句,但最后一條執行的代碼,一定是return(這么說是因為,如果沒有寫return,會隱式調用return = none,它默認寫在定義函數時的最后一句)。
5. 函數的作用域
5.1 作用域的概念
在自定義函數時,就涉及到一個問題,在函數內部定義的變量,在函數之外能不能使用。這個問題要分類討論。
先介紹一個定義,作用域,一個標識符的可見范圍,就是這個標識符的作用域,也叫做變量的作用域(個人覺得叫變量的作用范圍比較好理解)。
1
2
3
4
5
6
7
8
9
10
|
a = 5 def test(): b = 10 print (a, b) return b test( 10 ) 5 10 print (a) 5 print (b) #這里會報錯 |
這里a的作用域是整個程序,也可以說在整個程序的運行環境中都可見。因為定義a是在最外部,所以函數的內部,不管多少層嵌套函數,都可以使用a。
這種a叫做全局變量,它的作用域是全局,全局作用域就是在整個程序的運行環境中都可見。
再看b,在函數內部定義的變量,在全局中是不可見的,它只能在這個函數的內部使用,所以最后在全局中print(b)的話會報錯,并且異常是nameerror,說這個b沒有被定義過。
實際參數的作用域也是只在函數的內部,因為形參是在函數的內部,給形參傳參之后,它還是在函數的內部。
這種b就叫做局部變量,它只作用于當前的函數或者類,局部變量的作用范圍不能超過當前的局部作用域。
再看一個例子:
1
2
3
4
5
6
|
x = 1 def t(): x = 100 print (x) t() print (x) |
x在全局下定義過,但是函數內部對這個x又重新定義了。但是在全局中,x仍然是原來的值,這說明x=100這個標量是局部變量,作用域只在這個函數內部,對外部不可見。
5.2 函數嵌套和作用域
有的時候,定義一個函數,內部可能還要再定義一個函數:
1
2
3
4
5
6
7
8
9
10
|
a = 5 def test(): b = a + 5 print (b) def test2(): c = b + 5 print (c) return c d = test2() return a, b, d |
分析代碼可以按這種順序,碰到def定義函數,跳過,等出現了調用,再回頭看這個函數的定義。
看這個函數,有雙層嵌套,調用它:
1
|
test() #注意test2()在全局中是調用不出來的 |
全局中有個a = 5 ,它的作用域是全局,所以在函數內部也可以用它,得到b = 10,打印一下,然后看見def test2(),跳過,然后下面就是調用test2(),再返回來看test2()的定義,b的作用域是局部的,包括當前這層函數和內層函數,所以得到c = 15,作為return返回,用標識符d接收,最后test()這一層的return返回a, b, d。
執行結果如下:
1
2
3
4
|
test() 10 15 ( 5 , 10 , 15 ) #這個是return的值,是output |
test()這層的函數定義時,為啥不return c呢,是因為c是test2()這一層函數中定義的變量,是局部變量,對于外部是不可見的,如果test()中使用了c,且沒有定義過,就會報錯。
這樣會多多少少出現一些麻煩,所以根據需要,python給出了兩種聲明global和nonlocal。
global,它能把一個變量變成全局變量。
1
2
3
4
5
6
7
8
9
10
11
|
a = 10 def t(): global x global a x = 1 a = 100 return x print (x) #x是在函數內部定義的,但是在全局的環境下也可以使用 1 print (a) #在內部重新定義的a也會改變 100 |
但是定義函數,其中有一個目的就是封裝,和外部環境隔絕,通過傳參來計算,但是這樣用global定義為全局變量,有悖于這個理念,所以盡量別用global。
nonlocal,它能把一個變量變成局部變量,就是這個變量的可見范圍是最外層的函數,但是全局中不可見。
這里還要引入兩個概念,
自由變量,就是沒有在本地作用域中定義的變量
閉包,指在嵌套函數中,內層函數用到了外層函數定義的變量(注意是外層函數,不是全局中定義的)這一種現象。
1
2
3
4
5
6
7
8
9
10
|
a = 1 def t(): a = 10 print (a) def t1(): nonlocal a a = 100 print (a) t1() print (a) |
調用t():
1
2
3
4
5
6
|
print (a) t() 1 #這是在全局環境中的a,值沒變是a 10 #這是最外層函數的a 100 #這是聲明a是nonlocal之后,給a改變值后print的a 100 #這是在最外層函數中print的a,已經發生了改變,和內層函數的a同步 |
5.3 函數默認值參數的作用域
參數可以作文局部變量理解,當一個有默認值的形參,沒有接收到實參時,就會調用默認值:
1
2
3
4
5
6
7
8
9
10
|
def t(a = [ 1 , 2 , 3 , 4 ]): a.append( 5 ) print (a) t() #連續調用3次 t() t() [ 1 , 2 , 3 , 4 , 5 ] [ 1 , 2 , 3 , 4 , 5 , 5 ] [ 1 , 2 , 3 , 4 , 5 , 5 , 5 ] |
三次都使用的是形參的默認值,但是print的結果不一樣,那么就是說明默認值改變了。
這是因為,形參的默認值都會保存下來,可以查看,用__defaults__:
1
2
|
t().__defaults__ ([ 1 , 2 , 3 , 4 , 5 , 5 , 5 ],) #這是三次執行之后的默認值 |
可以看到,默認值是保存在一個元組中,如果元組中的元素是引用類型(比如list),那么儲存的是一個引用地址,后面通過引用地址對這個默認值修改的話,修改的是他們共用的數據。默認值通過這個引用地址去找數據時,找到的是改過的數據。
有時候我們需要這種改變,但是有時候不需要,所以有兩種解決辦法:
1
2
3
4
|
def t(a = [ 1 , 2 , 3 , 4 ]): a = a[:] #影子拷貝,拷貝一個a,修改就是在這個新的a中 a.append( 5 ) print (a) |
但是這個有弊端,如果默認值內部還有引用類型,影子拷貝并不能拷貝引用類型。
還有一種方法:
1
2
3
4
5
|
def t(a = none): if a is none: a = [ 1 , 2 , 3 , 4 ] a.append( 5 ) print (a) |
如果不指定a,那么a就用默認值none,就給a重新賦值一個[1,2,3,4],這時候引用地址已經換了,不是默認值的引用地址,但是這個新的引用地址就是我們想要的a的默認值,然后進行操作。
下次如果還是不指定a的話,a還是none,還會重新賦值成[1,2,3,4]。相當于雖然想要的a默認值是引用類型,但是對a操作不改變我們想要的a的默認值。
6. legb原則
在定義函數時出現了函數嵌套,對于每個變量的作用域就要了解清楚。
這個變量被用到時,就要在這個解釋器中搜尋這個變量是在哪一層賦值的,找個搜尋順序就是l local e enclosing g global b build-in,也就是說,
先在本地(這一層作用域)查找,
沒找到就在這個函數的外部函數中查找,
如果還是沒找到,就在全局中查找,
如果仍舊沒找到,就在build-in庫中查找。
比如一個典型的錯誤:
1
2
3
4
|
x = 5 def t(): print (x) x + = 5 |
函數內print要用到x,首先要在local中查找,查到了x,但是注意,此時的x是作用域為函數內部,不能使用外部的x=5,這是給x定義,先計算右邊,右邊的x依舊沒有被定義,所以以一個x它自己(沒有值)來定義它,就會報錯。
1
2
3
|
x = 5 def t(): print (x) |
如果這樣的話,函數內部local沒有,嵌套函數enclosing也沒有,那么就去global全局中找,找到了x=5,使用它。
至于build-in 是指我們用到的一些內建函數,print(),input()等,都是定義在build-in庫中的,要使用時,也是一層一層往上找,顯然l e g中都沒有,然后去b中找,找到了,使用它。
7. 銷毀函數
兩種方法:
第一個是重新定義同樣函數名的函數。
第二個是用del:
1
|
del t |
這樣就把上面定義的t給刪除了,和刪除變量一樣,只是切斷了標識符和內存中函數的聯系,刪除了這個標識符,使這個函數在內存中的引用計數減一,不是真的刪除內存中的函數。
希望本文所述對大家python程序設計有所幫助。
原文鏈接:https://blog.csdn.net/LittleHuang950620/article/details/81939996