什么是函數,函數說白了就是將一系列代碼封裝起來,實現代碼的重用。
什么是代碼重用?
假設我有這樣的需求:
但是我還是覺得太麻煩了,每次想吃飯的時候都要重復這樣的步驟。此時,我希望有這樣的機器:
將重復的工作封裝到一起,我們只要向機器里放入東西,就能得到我們想要的。
這也就是所謂的代碼重用。
自定義函數
知道了函數是干什么用的之后,我們就開始學習自定義函數,也就是動手來造這個神奇的機器。
看代碼示例:
def dfb(a): '''一系列操作''' return '一碗%s飯' %a a = dfb('米')
b = dfb('米') print a
print b
這樣我們就得到了兩碗飯,真是方便快捷。
現在來解釋里面都有什么:
1. def 是python的關鍵字,是專門用來自定義函數的。
2. dfb是函數名,用來以后調用的。
3.(a)中的a為函數的參數,為函數里面的操作提供數據的。
4.return用來返回一個對象,這個對象可以是函數的處理結果,也可以是函數的處理狀態等等。
1.def
沒什么好解釋的,語法規定。
2.函數名
函數名就類似於變量名。
例如我寫了一個函數,但我不調用它,那會怎么樣。
def dfb(a): '''一系列操作''' return '一碗%s飯' %a
什么也沒有輸出,那是不是意味着函數不存在呢?
我們看看內存里有沒有:
print id(dfb)
很明顯,函數在內存中,能夠找得到。
所以,當我們定義一個函數的時候,python就會將函數加載到內存中,只不過不調用的時候,函數內部的代碼就不執行。
很明顯,和變量賦值原理差不多,所以要注意一個問題:如果函數名和變量名沖突了,相當於重新賦值。而python解釋是從上到下的,也就是說此時誰在下面誰占用這個變量名。剩下的那個就只能在內存中等待垃圾回收了。
def dfb(a): '''一系列操作''' return '一碗%s飯' %a dfb = 1 print dfb
print dfb()
那么函數名的命名有什么要求嗎?
函數名的要求比變量名嚴格一點,除了遵守變量名的要求之外,還不能夠使用數字。
所以函數名只能使用字母和下划線(_),同時還要避開python的關鍵字。
另外,在pep8標准中,函數名提倡都要小寫。
2.函數的作用域
參數函數的一大重難點,很多人不明白所謂的形參和實參到底是怎么回事。
要明白其中的區別,首先還解釋函數的作用域是什么回事。
所謂的函數作用域,就是這樣。
python在執行函數里面的代碼的時候,會將其放在一個新的環境中。這個新環境就像一個虛擬機,虛擬機能夠訪問和修改本機中數據,前提是該數據是可修改的,但是python在函數調用完畢之后會自動銷毀這個環境。但是,如果我們在函數中試圖修改一個不可變的數據,也就是進行重新賦值的行為的話。
DEBUG = True a = [] b = 123 def test(): if DEBUG: a.append(1) b = 321 #重新賦值,先進行對象創建在進行引用更新。 test() print a print b
就像這樣,我們在函數內可以訪問到全局變量,也可以對可修改類型進行修改,但是當我們試圖為變量b重新賦值時,卻發現沒用效果。
因為函數在運行完畢之后就被銷毀了,而在函數里面新建的對象也跟着被銷毀了。所以我們為b重新賦值,打算新建一個對象來改變其引用時,卻發現該對象在出了函數以后就消失了,這樣b豈不是沒有引用了?python不會讓這樣的事情發生,所以就引入了作用域,函數內部創建的對象和外部是隔離的,互不影響的。
所以作用域問題單純只是針對對象新建問題。
也就是在函數內新建的對象會放到一個盒子中,這些對象只在函數內有效,且與外層對象隔離,所以做到了就算變量名沖突,在函數內的賦值也不會影響到外面的變量。
函數內的變量就是局部變量,外部的就是全局變量。所以在函數中定義的變量,也就是局部變量,只在函數內部有效。
2.形參和實參
形參,字面意思,指的是形式上的。
實參,則是實際上的。
看下面這個例子。
a = 123 def dfb(a): '''一系列操作''' return '一碗%s飯' %a b = '米' print dfb(b)
我們傳參的時候,相當於在函數內部進行了 a = b 的操作。
因為函數是有作用域的,雖然外部的a=123,但是函數內部是相對於a=b='米',是一個局部變量。
而局部變量在函數執行完畢就銷毀了,所以它是形式上的,僅僅只是函數內部處理時用的,更像是一個標記,所以稱為形參。而當我們調用函數時,實際給的參數,如這里的 dfb(b) 中的b這個實際的參數,是實際在內存中有的,所以就稱為實參。
2.global
如果我要強制在函數里面進行全局變量的聲明怎么辦?
a = 123 def dfb(a): '''一系列操作''' b = 1 return '一碗%s飯' %a dfb('米') print b
此時可以使用global關鍵字:
a = 123 def dfb(a): '''一系列操作''' global b #我先聲明我要創建一個全局變量b b = 1 #然后我再為b賦值 return '一碗%s飯' %a dfb('米') print b
這樣就可以在函數內創建全局變量了。
此時有機智的同學就要問了,那我在函數內部聲明的全局變量和之前的沖突,是不是會重新賦值呢?
答案:是的。
但要注意一個問題:
a = 123 def dfb(a): '''一系列操作''' global a #我聲明全局變量a,想要覆蓋之前的賦值,而傳參的時候,相對於進行了a=b的操作 return '一碗%s飯' %a b = '米' print dfb(b)
然而報錯了:
說明我們不能將形式參數聲明為全局的。
那我們改一下形式參數的名稱:
a = 123 def dfb(c): '''一系列操作''' global a #我聲明全局變量a,想要覆蓋之前的賦值 a = c return '一碗%s飯' %a b = '米' dfb(b) print a
可以了,說明我們的思路是正確的,在函數內聲明全局變量確實會覆蓋已經有的變量。
3.傳參
傳參是函數的又一大重點和難點。
傳參是為了能使函數適用更多的情況,我們在上面的示例中都寫列帶參數的函數,是不是說函數一定要參數才行呢?
def test(): print '然而我並沒有參數' test()
可以看出沒有參數也是可以的,只是沒有參數的時候,無論怎么調用得到的結果都是一樣的,靈活性太低,所以為了提高靈活性,就有了參數存在的必要。
而參數又分為普通參數,默認參數,動態參數,下面逐一說明。
1.普通參數
def test(a,b,c): print a print b print c test(1,2,3)
可以看出參數是按照順序傳遞進去的,這種寫法就叫普通函數。
但是這里有一個問題:
def test(a,b,c): print a print b print c test(1,2)
本來要傳3個參數的,但是我僅傳了2個,這樣就報錯了。但我不想這樣,我希望當我不傳參數的時候,參數有一個默認的值,此時就需要默認參數了。
2.默認參數
def test(a,b,c=3): print a print b print c test(1,2)
傳參就相當於為形參賦上實參的值,你給了參數,我就按順序執行 a = 1 ,b = 2,但此時c已經賦值為3了,所以就算不傳,參數的數量也夠了。
當然也可以將參數傳夠, text(1,2,3) 就相當於為c重新賦值了。這樣就能達到你傳了參數就按照傳的參數來處理,而沒傳就按默認的值處理。
所以我們可以為全部的參數都設置默認值。
但是可能會出現這樣的情況:
def test(a=1,b,c=3): print a print b print c test(1,2)
並不允許這樣的寫法,因為這樣沒有默認值的參數很難處理,所以當默認參數和普通參數同時存在時,要將普通參數放在前面:
def test(b,a=1,c=3): print a print b print c test(1,2)
之所以要規定這樣寫,是因為要配合傳參的方法:我們可以顯式地規定哪個值是傳給哪個參數的:
def test(b,a=1,c=3): print a print b print c test(a=1,b=2,c=3)
如果是這樣的顯式傳參的話,傳參的順序是任意的。也就是 text(a=1,c=3,b=2) 也可以,反正最后賦值的結果是一樣的。
但是,如果是普通傳參和顯式傳參配合使用時,就必須將默認傳參放在開頭: text(2,a=1,c=3,) 普通的按順序傳,顯式可以不按順序傳。這也就是我們在寫函數的時候要求普通的參數都放在前面,默認的參數都放在后面,就是為了和這里對應。
3.動態參數
因為我們的函數最后可能並不是只有自己用,而是給用戶或其他人調用,但是其他人不一定了解我這里接受多少個參數。
傳少了我們可以使用默認參數來解決,但是傳多了怎么辦,一旦傳多了就報錯用戶體驗就不好了,為了提高函數的適應能力,就出現了動態參數。
def test(a,*args,**kwargs): print a test(1,2,c=3)
這樣函數的適應性就更高了,那么這里的 *args 和 **kwargs 分別是什么意思。
我們先來看去是什么類型:
def test(a,*args,**kwargs): print type(args),type(kwargs) test(1,2,c=3)
它們是元祖和字典,這里注意前面的*號只是表示這個是什么類型的,*表示元祖,**代表字典,而真正的變量名是*后面的。這里並沒有規定一定要用args命名元祖,kwargs命名字典。但是這是個規范的寫法,為的是讓別的程序員能一眼看出這是個什么東西。
接下來我們看看里面存的是什么:
def test(a,*args,**kwargs): print args print kwargs test(1,2,c=3)
它將默認傳參方式多傳的值放在一個元祖里,而用顯式傳參多傳的用其參數名為鍵,參數值為值,組成了字典。
你可以無視它們,也可以將處理它們,賦值操作也好,循環也好,成員判斷也好,各種需要看個人。
4.return
當函數遇到return語句時,表示函數執行完畢,此時返回一個對象,然后銷毀函數的執行環境。
但是你在上面的示例中看到也有這樣的寫法:
def test(): print '我並沒寫return' test()
發現沒有return語句還是能執行的,在調用的時候輸出了東西,執行一遍后也停止了。
注意,在函數內沒有寫return語句的時候,默認return的是一個空對象。也就是就算沒寫,python內部也做了處理。
此時,有部分人分不清函數的輸出和返回值的區別。
這樣說吧,在函數里print之類的操作能夠輸出內容,是因為雖然函數的執行環境是獨立的,但代碼還是有效的。外部能進行的操作,函數內部也可以。但是並不是所有的函數在執行完畢后都有如此明顯的輸出效果,此時我們需要查看函數是否成功,或者說我放了米進去,你操作一番之后總要把飯給我拿出來吧。
這就是函數中return的意義。返回一個對象。這個對象可以是對執行狀態的說明,也可以是處理后的結果等等。
def test(): '''一系列操作''' return '搞定了' test()
但運行還是沒看到東西。
那是因為函數雖然返回了對象,但是這個對象還在內存中,你並沒有把它拿出了,當然什么也看不到。
print test()
這樣就看到了,當然你可以進行變量的賦值,如 a = test() ,之后調用變量a就行了。當然返回結果多用True和False代表成功和失敗,然后可以和條件控制語句配合使用。
def test(): '''一系列操作''' print test()
當然不寫就默認返回空對象None了。
def test(a,b): c = a + b return c print test(1,2)
返回處理后的結果也是可以的,反正看個人需求。
最后,雖說遇到return代表函數結束,但並意味着一個函數里面只有一個return。
def test(a,b): if a != 0: return a + b return 'a不能為0' print test(1,2) print test(0,2)
可以配合條件控制語句實現不同情況返回不同的對象,這樣就函數就能處理更多的情況了。
匿名函數:
有些情況下,我只想用函數處理一下很簡單的業務,這個時候完整地寫一個函數感覺會增大代碼量,此時可以使用匿名函數進行處理。
python 使用 lambda 來創建匿名函數:
1.lambda只是一個表達式,函數體比def簡單很多。
2.lambda的主體是一個表達式,而不是一個代碼塊。僅僅能在lambda表達式中封裝有限的邏輯進去。
3.lambda函數擁有自己的命名空間,且不能訪問自有參數列表之外或全局命名空間里的參數。
4.雖然lambda函數看起來只能寫一行,卻不等同於C或C++的內聯函數,后者的目的是調用小函數時不占用棧內存從而增加運行效率。
語法:
lambda [arg1 [,arg2,.....argn]]:expression
示例:
# 可寫函數說明 sum = lambda arg1, arg2: arg1 + arg2 # 調用sum函數 print "相加后的值為 : ", sum( 10, 20 ) print "相加后的值為 : ", sum( 20, 20 )
雖說是匿名函數,但是還是需要將其賦給一個變量,否則無法調用。而匿名函數里面的變量也是局部變量,和完整的函數也沒有什么區別。
匿名函數建議只在處理簡單功能的時候使用,太過復雜的還是使用完整的函數吧。
pass語句
Python pass是空語句,是為了保持程序結構的完整性。pass 不做任何事情,一般用做占位語句。
def test(): pass #我想要用這個函數處理某些事,但我暫時沒想好怎么寫,就先占個坑
當然,不僅函數可以使用,流程控制中也可以使用。
if a <= 10: pass else: print 'a大於10'
還是那句話,僅起占位的作用。但對於某些語法結構來說,必須要有子語句的存在,所以pass在這個時候就很有用了。
另外,pass也表示什么都不做。
關於自定義函數就先說到這里,如有什么錯誤和需要補充的后面會相應修改。