17.python自定義函數


  什么是函數,函數說白了就是將一系列代碼封裝起來,實現代碼的重用。

  什么是代碼重用?

  假設我有這樣的需求:

  但是我還是覺得太麻煩了,每次想吃飯的時候都要重復這樣的步驟。此時,我希望有這樣的機器:

  

    將重復的工作封裝到一起,我們只要向機器里放入東西,就能得到我們想要的。

  這也就是所謂的代碼重用。


 

自定義函數

  知道了函數是干什么用的之后,我們就開始學習自定義函數,也就是動手來造這個神奇的機器。

  看代碼示例:

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也表示什么都不做。

 


  關於自定義函數就先說到這里,如有什么錯誤和需要補充的后面會相應修改。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM