草根學Python(六) 函數

前言

前天創了個 Python 微信討論群,以為沒人進的,哈哈,想不到還真有小夥伴進群學習討論。如果想進群,可以加我微信: androidwed ,拉進群,就不貼微信群二維碼了,一是會失效,二影響文章。

目錄

一、Python 自定義函數的基本步驟

函數是組織好的,可重複使用的,用來實現單一,或相關聯功能的代碼段。

自定義函數,基本有以下規則步驟:

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

語法示例:

def functionname( parameters ):n "函數_文檔字元串"n function_suiten return [expression]n

實例:

  1. def 定義一個函數,給定一個函數名 sum
  2. 聲明兩個參數 num1 和 num2
  3. 函數的第一行語句進行函數說明:兩數之和
  4. 最終 return 語句結束函數,並返回兩數之和

def sum(num1,num2):n "兩數之和"n return num1+num2nn# 調用函數nprint(sum(5,6))n

輸出結果:

11n

二、函數傳值問題

先看一個例子:

# -*- coding: UTF-8 -*-ndef chagne_number( b ):n b = 1000nnb = 1nchagne_number(b)nprint( b )n

最後輸出的結果為:

1n

這裡可能有些人會有疑問,為啥不是通過函數chagne_number更改了 b

的值嗎?為啥沒有變化,輸出的結果還是 1 ,這個問題很多編程語言都會講到,原理解釋也是差不多的。

這裡主要是函數參數的傳遞中,傳遞的是類型對象,之前也介紹了 Python 中基本的數據類型等。而這些類型對象可以分為可更改類型和不可更改的類型

在 Python 中,字元串,整形,浮點型,tuple 是不可更改的對象,而 list , dict 等是可以更改的對象。

例如:

不可更改的類型:變數賦值 a = 1,其實就是生成一個整形對象 1 ,然後變數 a 指向 1,當 a = 1000 其實就是再生成一個整形對象 1000,然後改變 a 的指向,不再指向整形對象 1 ,而是指向 1000,最後 1 會被丟棄

可更改的類型:變數賦值 a = [1,2,3,4,5,6] ,就是生成一個對象 list ,list 裡面有 6 個元素,而變數 a 指向 list ,a[2] = 5則是將 list a 的第三個元素值更改,這裡跟上面是不同的,並不是將 a 重新指向,而是直接修改 list 中的元素值。

這也將影響到函數中參數的傳遞了:

不可更改的類型:類似 c++ 的值傳遞,如 整數、字元串、元組。如fun(a),傳遞的只是 a 的值,沒有影響 a 對象本身。比如在 fun(a)內部修改 a 的值,只是修改另一個複製的對象,不會影響 a 本身。

可更改的類型:類似 c++ 的引用傳遞,如 列表,字典。如 fun(a),則是將 a 真正的傳過去,修改後 fun 外部的 a 也會受影響

因此,在一開始的例子中,b = 1,創建了一個整形對象 1 ,變數 b 指向了這個對象,然後通過函數 chagne_number 時,按傳值的方式複製了變數 b ,傳遞的只是 b 的值,並沒有影響到 b 的本身。具體可以看下修改後的實例,通過列印的結果更好的理解。

# -*- coding: UTF-8 -*-ndef chagne_number( b ):n print(函數中一開始 b 的值:{} .format( b ) )n b = 1000n print(函數中 b 賦值後的值:{} .format( b ) )nnnb = 1nchagne_number( b )nprint( 最後輸出 b 的值:{} .format( b ) )n

列印的結果:

函數中一開始 b 的值:1n函數中 b 賦值後的值:1000n最後輸出 b 的值:1n

當然,如果參數中的是可更改的類型,那麼調用了這個函數後,原來的值也會被更改,具體實例如下:

# -*- coding: UTF-8 -*-nndef chagne_list( b ):n print(函數中一開始 b 的值:{} .format( b ) )n b.append(1000)n print(函數中 b 賦值後的值:{} .format( b ) )nnnb = [1,2,3,4,5]nchagne_list( b )nprint( 最後輸出 b 的值:{} .format( b ) )n

輸出的結果:

函數中一開始 b 的值:[1, 2, 3, 4, 5]n函數中 b 賦值後的值:[1, 2, 3, 4, 5, 1000]n最後輸出 b 的值:[1, 2, 3, 4, 5, 1000]n

三、函數返回值

通過上面的學習,可以知道通過 return [表達式] 語句用於退出函數,選擇性地向調用方返回一個表達式。不帶參數值的 return 語句返回 None。

具體示例:

# -*- coding: UTF-8 -*-nndef sum(num1,num2):n # 兩數之和n if not (isinstance (num1,(int ,float)) or isinstance (num2,(int ,float))):n raise TypeError(參數類型錯誤)n return num1+num2nnprint(sum(1,2))n

返回結果:

3n

這個示例,還通過內置函數isinstance()進行數據類型檢查,檢查調用函數時參數是否是整形和浮點型。如果參數類型不對,會報錯,提示 參數類型錯誤,如圖:

當然,函數也可以返回多個值,具體實例如下:

# -*- coding: UTF-8 -*-nndef division ( num1, num2 ):n # 求商與餘數n a = num1 % num2n b = (num1-a) / num2n return b , a nnnum1 , num2 = division(9,4)ntuple1 = division(9,4)nnprint (num1,num2)nprint (tuple1)n

輸出的值:

2.0 1n(2.0, 1)n

認真觀察就可以發現,儘管從第一個輸出值來看,返回了多個值,實際上是先創建了一個元組然後返回的。回憶一下,元組是可以直接用逗號來創建的,觀察例子中的 ruturn ,可以發現實際上我們使用的是逗號來生成一個元組。

四、函數的參數

1、默認值參數

有時候,我們自定義的函數中,如果調用的時候沒有設置參數,需要給個默認值,這時候就需要用到默認值參數了。

# -*- coding: UTF-8 -*-nndef print_user_info( name , age , sex = ):n # 列印用戶信息n print(昵稱:{}.format(name) , end = )n print(年齡:{}.format(age) , end = )n print(性別:{}.format(sex))n return;nn# 調用 print_user_info 函數nnprint_user_info( 兩點水 , 18 , )nprint_user_info( 三點水 , 25 )n

輸出結果:

昵稱:兩點水 年齡:18 性別:女n昵稱:三點水 年齡:25 性別:男n

可以看到,當你設置了默認參數的時候,在調用函數的時候,不傳該參數,就會使用默認值。但是這裡需要注意的一點是:只有在形參表末尾的那些參數可以有默認參數值,也就是說你不能在聲明函數形參的時候,先聲明有默認值的形參而後聲明沒有默認值的形參。這是因為賦給形參的值是根據位置而賦值的。例如,def func(a, b=1) 是有效的,但是 def func(a=1, b) 是 無效 的。

默認值參數就這樣結束了嗎?還沒有的,細想一下,如果參數中是一個可修改的容器比如一個 lsit (列表)或者 dict (字典),那麼我們使用什麼來作為默認值呢?我們可以使用 None 作為默認值。就像下面這個例子一樣:

# 如果 b 是一個 list ,可以使用 None 作為默認值ndef print_info( a , b = None ):n if b is None :n b=[]n return;n

認真看下例子,會不會有這樣的疑問呢?在參數中我們直接 b=[] 不就行了嗎?也就是寫成下面這個樣子:

def print_info( a , b = [] ):n return;n

對不對呢?運行一下也沒發現錯誤啊,可以這樣寫嗎?這裡需要特別注意的一點:默認參數的值是不可變的對象,比如None、True、False、數字或字元串,如果你像上面的那樣操作,當默認值在其他地方被修改後你將會遇到各種麻煩。這些修改會影響到下次調用這個函數時的默認值。

示例如下:

# -*- coding: UTF-8 -*-nndef print_info( a , b = [] ):n print(b)n return b ;nnresult = print_info(1)nnresult.append(error)nnprint_info(2)n

輸出的結果:

[]n[error]n

認真觀察,你會發現第二次輸出的值根本不是你想要的,因此切忌不能這樣操作。

還有一點,有時候我就是不想要默認值啊,只是想單單判斷默認參數有沒有值傳遞進來,那該怎麼辦?我們可以這樣做:

_no_value =object()nndef print_info( a , b = _no_value ):n if b is _no_value :n print(b 沒有賦值)n return;n

這裡的 object 是python中所有類的基類。 你可以創建 object 類的實例,但是這些實例沒什麼實際用處,因為它並沒有任何有用的方法, 也沒有任何實例數據(因為它沒有任何的實例字典,你甚至都不能設置任何屬性值)。 你唯一能做的就是測試同一性。也正好利用這個特性,來判斷是否有值輸入。

2、關鍵字參數

在 Python 中,可以通過參數名來給函數傳遞參數,而不用關心參數列表定義時的順序,這被稱之為關鍵字參數。使用關鍵參數有兩個優勢 :

一、由於我們不必擔心參數的順序,使用函數變得更加簡單了。

二、假設其他參數都有默認值,我們可以只給我們想要的那些參數賦值

# -*- coding: UTF-8 -*-nndef print_user_info( name , age , sex = ):n # 列印用戶信息n print(昵稱:{}.format(name) , end = )n print(年齡:{}.format(age) , end = )n print(性別:{}.format(sex))n return;nn# 調用 print_user_info 函數nnprint_user_info( name = 兩點水 ,age = 18 , sex = )nprint_user_info( name = 兩點水 ,sex = , age = 18 )n

輸出的值:

昵稱:兩點水 年齡:18 性別:女n昵稱:兩點水 年齡:18 性別:女n

3、不定長參數

有時我們在設計函數介面的時候,可會需要可變長的參數。也就是說,我們事先無法確定傳入的參數個數。Python 提供了一種元組的方式來接受沒有直接定義的參數。這種方式在參數前邊加星號 * 。如果在函數調用時沒有指定參數,它就是一個空元組。我們也可以不向函數傳遞未命名的變數。

例如:

# -*- coding: UTF-8 -*-nndef print_user_info( name , age , sex = , * hobby):n # 列印用戶信息n print(昵稱:{}.format(name) , end = )n print(年齡:{}.format(age) , end = )n print(性別:{}.format(sex) ,end = )n print(愛好:{}.format(hobby))n return;nn# 調用 print_user_info 函數nprint_user_info( 兩點水 ,18 , , 打籃球,打羽毛球,跑步)n

輸出的結果:

昵稱:兩點水 年齡:18 性別:女 愛好:(打籃球, 打羽毛球, 跑步)n

通過輸出的結果可以知道,*hobby是可變參數,且 hobby其實就是一個 tuple (元祖)

可變長參數也支持關鍵參數,沒有被定義的關鍵參數會被放到一個字典里。這種方式即是在參數前邊加 **,更改上面的示例如下:

# -*- coding: UTF-8 -*-nndef print_user_info( name , age , sex = , ** hobby ):n # 列印用戶信息n print(昵稱:{}.format(name) , end = )n print(年齡:{}.format(age) , end = )n print(性別:{}.format(sex) ,end = )n print(愛好:{}.format(hobby))n return;nn# 調用 print_user_info 函數nprint_user_info( name = 兩點水 , age = 18 , sex = , hobby = (打籃球,打羽毛球,跑步))n

輸出的結果:

昵稱:兩點水 年齡:18 性別:女 愛好:{hobby: (打籃球, 打羽毛球, 跑步)}n

通過對比上面的例子和這個例子,可以知道,*hobby是可變參數,且 hobby其實就是一個 tuple (元祖),**hobby是關鍵字參數,且 hobby 就是一個 dict (字典)

4、只接受關鍵字參數

關鍵字參數使用起來簡單,不容易參數出錯,那麼有些時候,我們定義的函數希望某些參數強制使用關鍵字參數傳遞,這時候該怎麼辦呢?

將強制關鍵字參數放到某個*參數或者單個*後面就能達到這種效果,比如:

# -*- coding: UTF-8 -*-nndef print_user_info( name , *, age , sex = ):n # 列印用戶信息n print(昵稱:{}.format(name) , end = )n print(年齡:{}.format(age) , end = )n print(性別:{}.format(sex))n return;nn# 調用 print_user_info 函數nprint_user_info( name = 兩點水 ,age = 18 , sex = )nn# 這種寫法會報錯,因為 age ,sex 這兩個參數強制使用關鍵字參數n#print_user_info( 兩點水 , 18 , 女 )nprint_user_info(兩點水,age=22,sex=)n

通過例子可以看,如果 age , sex 不適用關鍵字參數是會報錯的。

很多情況下,使用強制關鍵字參數會比使用位置參數表意更加清晰,程序也更加具有可讀性。使用強制關鍵字參數也會比使用 **kw 參數更好且強制關鍵字參數在一些更高級場合同樣也很有用。

五、匿名函數

有沒有想過定義一個很短的回調函數,但又不想用 def 的形式去寫一個那麼長的函數,那麼有沒有快捷方式呢?答案是有的。

python 使用 lambda 來創建匿名函數,也就是不再使用 def 語句這樣標準的形式定義一個函數。

匿名函數主要有以下特點:

  • lambda 只是一個表達式,函數體比 def 簡單很多。
  • lambda 的主體是一個表達式,而不是一個代碼塊。僅僅能在 lambda 表達式中封裝有限的邏輯進去。
  • lambda 函數擁有自己的命名空間,且不能訪問自有參數列表之外或全局命名空間里的參數。

基本語法

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

示例:

# -*- coding: UTF-8 -*-nnsum = lambda num1 , num2 : num1 + num2;nnprint( sum( 1 , 2 ) )n

輸出的結果:

3n

注意:儘管 lambda 表達式允許你定義簡單函數,但是它的使用是有限制的。 你只能指定單個表達式,它的值就是最後的返回值。也就是說不能包含其他的語言特性了, 包括多個語句、條件表達式、迭代以及異常處理等等。

匿名函數中,有一個特別需要注意的問題,比如,把上面的例子改一下:

# -*- coding: UTF-8 -*-nnnum2 = 100nsum1 = lambda num1 : num1 + num2 ;nnnum2 = 10000nsum2 = lambda num1 : num1 + num2 ;nnprint( sum1( 1 ) )nprint( sum2( 1 ) )n

你會認為輸出什麼呢?第一個輸出是 101,第二個是 10001,結果不是的,輸出的結果是這樣:

10001n10001n

這主要在於 lambda 表達式中的 num2 是一個自由變數,在運行時綁定值,而不是定義時就綁定,這跟函數的默認值參數定義是不同的。所以建議還是遇到這種情況還是使用第一種解法。

推薦閱讀:

我數學很差,最近報了培訓班在學Python編程,上課都能聽懂,自己寫程序的時候感覺特別難是怎麼回事?
if __name__ == __main__ 如何正確理解?
如何使用爬蟲監控一系列網站的更新情況?
python中的類型是怎麼實現不用顯式定義,動態確定數據類型的?

TAG:Python入门 | Python教程 | Python |