前言
前天創了個 Python 微信討論群,以為沒人進的,哈哈,想不到還真有小夥伴進群學習討論。如果想進群,可以加我微信: androidwed ,拉進群,就不貼微信群二維碼了,一是會失效,二影響文章。
目錄
一、Python 自定義函式的基本步驟
函式是組織好的,可重複使用的,用來實現單一,或相關聯功能的程式碼段。
自定義函式,基本有以下規則步驟:
- 函式程式碼塊以 def 關鍵詞開頭,後接函式識別符號名稱和圓括號()
- 任何傳入引數和自變數必須放在圓括號中間。圓括號之間可以用於定義引數
- 函式的第一行語句可以選擇性地使用文件字串(用於存放函式說明)
- 函式內容以冒號起始,並且縮排
- return [表示式] 結束函式,選擇性地返回一個值給呼叫方。不帶表示式的 return 相當於返回 None。
語法示例:
def functionname( parameters ):
"函式_文件字串"
function_suite
return [expression]複製程式碼
例項:
- def 定義一個函式,給定一個函式名 sum
- 宣告兩個引數 num1 和 num2
- 函式的第一行語句進行函式說明:兩數之和
- 最終 return 語句結束函式,並返回兩數之和
def sum(num1,num2):
"兩數之和"
return num1+num2
# 呼叫函式
print(sum(5,6))複製程式碼
輸出結果:
11複製程式碼
二、函式傳值問題
先看一個例子:
# -*- coding: UTF-8 -*-
def chagne_number( b ):
b = 1000
b = 1
chagne_number(b)
print( b )複製程式碼
最後輸出的結果為:
1複製程式碼
這裡可能有些人會有疑問,為啥不是通過函式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 -*-
def chagne_number( b ):
print('函式中一開始 b 的值:{}' .format( b ) )
b = 1000
print('函式中 b 賦值後的值:{}' .format( b ) )
b = 1
chagne_number( b )
print( '最後輸出 b 的值:{}' .format( b ) )複製程式碼
列印的結果:
函式中一開始 b 的值:1
函式中 b 賦值後的值:1000
最後輸出 b 的值:1複製程式碼
當然,如果引數中的是可更改的型別,那麼呼叫了這個函式後,原來的值也會被更改,具體例項如下:
# -*- coding: UTF-8 -*-
def chagne_list( b ):
print('函式中一開始 b 的值:{}' .format( b ) )
b.append(1000)
print('函式中 b 賦值後的值:{}' .format( b ) )
b = [1,2,3,4,5]
chagne_list( b )
print( '最後輸出 b 的值:{}' .format( b ) )複製程式碼
輸出的結果:
函式中一開始 b 的值:[1, 2, 3, 4, 5]
函式中 b 賦值後的值:[1, 2, 3, 4, 5, 1000]
最後輸出 b 的值:[1, 2, 3, 4, 5, 1000]複製程式碼
三、函式返回值
通過上面的學習,可以知道通過 return [表示式] 語句用於退出函式,選擇性地向呼叫方返回一個表示式。不帶引數值的 return 語句返回 None。
具體示例:
# -*- coding: UTF-8 -*-
def sum(num1,num2):
# 兩數之和
if not (isinstance (num1,(int ,float)) or isinstance (num2,(int ,float))):
raise TypeError('引數型別錯誤')
return num1+num2
print(sum(1,2))複製程式碼
返回結果:
3複製程式碼
這個示例,還通過內建函式isinstance()
進行資料型別檢查,檢查呼叫函式時引數是否是整形和浮點型。如果引數型別不對,會報錯,提示 引數型別錯誤
,如圖:
當然,函式也可以返回多個值,具體例項如下:
# -*- coding: UTF-8 -*-
def division ( num1, num2 ):
# 求商與餘數
a = num1 % num2
b = (num1-a) / num2
return b , a
num1 , num2 = division(9,4)
tuple1 = division(9,4)
print (num1,num2)
print (tuple1)複製程式碼
輸出的值:
2.0 1
(2.0, 1)複製程式碼
認真觀察就可以發現,儘管從第一個輸出值來看,返回了多個值,實際上是先建立了一個元組然後返回的。回憶一下,元組是可以直接用逗號來建立的,觀察例子中的 ruturn ,可以發現實際上我們使用的是逗號來生成一個元組。
四、函式的引數
1、預設值引數
有時候,我們自定義的函式中,如果呼叫的時候沒有設定引數,需要給個預設值,這時候就需要用到預設值引數了。
# -*- coding: UTF-8 -*-
def print_user_info( name , age , sex = '男' ):
# 列印使用者資訊
print('暱稱:{}'.format(name) , end = ' ')
print('年齡:{}'.format(age) , end = ' ')
print('性別:{}'.format(sex))
return;
# 呼叫 print_user_info 函式
print_user_info( '兩點水' , 18 , '女')
print_user_info( '三點水' , 25 )複製程式碼
輸出結果:
暱稱:兩點水 年齡:18 性別:女
暱稱:三點水 年齡:25 性別:男複製程式碼
可以看到,當你設定了預設引數的時候,在呼叫函式的時候,不傳該引數,就會使用預設值。但是這裡需要注意的一點是:只有在形參表末尾的那些引數可以有預設引數值,也就是說你不能在宣告函式形參的時候,先宣告有預設值的形參而後宣告沒有預設值的形參。這是因為賦給形參的值是根據位置而賦值的。例如,def func(a, b=1) 是有效的,但是 def func(a=1, b) 是 無效 的。
預設值引數就這樣結束了嗎?還沒有的,細想一下,如果引數中是一個可修改的容器比如一個 lsit (列表)或者 dict (字典),那麼我們使用什麼來作為預設值呢?我們可以使用 None 作為預設值。就像下面這個例子一樣:
# 如果 b 是一個 list ,可以使用 None 作為預設值
def print_info( a , b = None ):
if b is None :
b=[]
return;複製程式碼
認真看下例子,會不會有這樣的疑問呢?在引數中我們直接 b=[]
不就行了嗎?也就是寫成下面這個樣子:
def print_info( a , b = [] ):
return;複製程式碼
對不對呢?執行一下也沒發現錯誤啊,可以這樣寫嗎?這裡需要特別注意的一點:預設引數的值是不可變的物件,比如None、True、False、數字或字串,如果你像上面的那樣操作,當預設值在其他地方被修改後你將會遇到各種麻煩。這些修改會影響到下次呼叫這個函式時的預設值。
示例如下:
# -*- coding: UTF-8 -*-
def print_info( a , b = [] ):
print(b)
return b ;
result = print_info(1)
result.append('error')
print_info(2)複製程式碼
輸出的結果:
[]
['error']複製程式碼
認真觀察,你會發現第二次輸出的值根本不是你想要的,因此切忌不能這樣操作。
還有一點,有時候我就是不想要預設值啊,只是想單單判斷預設引數有沒有值傳遞進來,那該怎麼辦?我們可以這樣做:
_no_value =object()
def print_info( a , b = _no_value ):
if b is _no_value :
print('b 沒有賦值')
return;複製程式碼
這裡的 object
是python中所有類的基類。 你可以建立 object
類的例項,但是這些例項沒什麼實際用處,因為它並沒有任何有用的方法, 也沒有任何例項資料(因為它沒有任何的例項字典,你甚至都不能設定任何屬性值)。 你唯一能做的就是測試同一性。也正好利用這個特性,來判斷是否有值輸入。
2、關鍵字引數
在 Python 中,可以通過引數名來給函式傳遞引數,而不用關心引數列表定義時的順序,這被稱之為關鍵字引數。使用關鍵引數有兩個優勢 :
一、由於我們不必擔心引數的順序,使用函式變得更加簡單了。
二、假設其他引數都有預設值,我們可以只給我們想要的那些引數賦值
# -*- coding: UTF-8 -*-
def print_user_info( name , age , sex = '男' ):
# 列印使用者資訊
print('暱稱:{}'.format(name) , end = ' ')
print('年齡:{}'.format(age) , end = ' ')
print('性別:{}'.format(sex))
return;
# 呼叫 print_user_info 函式
print_user_info( name = '兩點水' ,age = 18 , sex = '女')
print_user_info( name = '兩點水' ,sex = '女', age = 18 )複製程式碼
輸出的值:
暱稱:兩點水 年齡:18 性別:女
暱稱:兩點水 年齡:18 性別:女複製程式碼
3、不定長引數
有時我們在設計函式介面的時候,可會需要可變長的引數。也就是說,我們事先無法確定傳入的引數個數。Python 提供了一種元組的方式來接受沒有直接定義的引數。這種方式在引數前邊加星號 *
。如果在函式呼叫時沒有指定引數,它就是一個空元組。我們也可以不向函式傳遞未命名的變數。
例如:
# -*- coding: UTF-8 -*-
def print_user_info( name , age , sex = '男' , * hobby):
# 列印使用者資訊
print('暱稱:{}'.format(name) , end = ' ')
print('年齡:{}'.format(age) , end = ' ')
print('性別:{}'.format(sex) ,end = ' ' )
print('愛好:{}'.format(hobby))
return;
# 呼叫 print_user_info 函式
print_user_info( '兩點水' ,18 , '女', '打籃球','打羽毛球','跑步')複製程式碼
輸出的結果:
暱稱:兩點水 年齡:18 性別:女 愛好:('打籃球', '打羽毛球', '跑步')複製程式碼
通過輸出的結果可以知道,*hobby
是可變引數,且 hobby其實就是一個 tuple (元祖)
可變長引數也支援關鍵引數,沒有被定義的關鍵引數會被放到一個字典裡。這種方式即是在引數前邊加 **
,更改上面的示例如下:
# -*- coding: UTF-8 -*-
def print_user_info( name , age , sex = '男' , ** hobby ):
# 列印使用者資訊
print('暱稱:{}'.format(name) , end = ' ')
print('年齡:{}'.format(age) , end = ' ')
print('性別:{}'.format(sex) ,end = ' ' )
print('愛好:{}'.format(hobby))
return;
# 呼叫 print_user_info 函式
print_user_info( name = '兩點水' , age = 18 , sex = '女', hobby = ('打籃球','打羽毛球','跑步'))複製程式碼
輸出的結果:
暱稱:兩點水 年齡:18 性別:女 愛好:{'hobby': ('打籃球', '打羽毛球', '跑步')}複製程式碼
通過對比上面的例子和這個例子,可以知道,*hobby
是可變引數,且 hobby其實就是一個 tuple (元祖),**hobby
是關鍵字引數,且 hobby 就是一個 dict (字典)
4、只接受關鍵字引數
關鍵字引數使用起來簡單,不容易引數出錯,那麼有些時候,我們定義的函式希望某些引數強制使用關鍵字引數傳遞,這時候該怎麼辦呢?
將強制關鍵字引數放到某個*
引數或者單個*
後面就能達到這種效果,比如:
# -*- coding: UTF-8 -*-
def print_user_info( name , *, age , sex = '男' ):
# 列印使用者資訊
print('暱稱:{}'.format(name) , end = ' ')
print('年齡:{}'.format(age) , end = ' ')
print('性別:{}'.format(sex))
return;
# 呼叫 print_user_info 函式
print_user_info( name = '兩點水' ,age = 18 , sex = '女' )
# 這種寫法會報錯,因為 age ,sex 這兩個引數強制使用關鍵字引數
#print_user_info( '兩點水' , 18 , '女' )
print_user_info('兩點水',age='22',sex='男')複製程式碼
通過例子可以看,如果 age
, sex
不適用關鍵字引數是會報錯的。
很多情況下,使用強制關鍵字引數會比使用位置參數列意更加清晰,程式也更加具有可讀性。使用強制關鍵字引數也會比使用 **kw
引數更好且強制關鍵字引數在一些更高階場合同樣也很有用。
五、匿名函式
有沒有想過定義一個很短的回撥函式,但又不想用 def
的形式去寫一個那麼長的函式,那麼有沒有快捷方式呢?答案是有的。
python 使用 lambda 來建立匿名函式,也就是不再使用 def 語句這樣標準的形式定義一個函式。
匿名函式主要有以下特點:
- lambda 只是一個表示式,函式體比 def 簡單很多。
- lambda 的主體是一個表示式,而不是一個程式碼塊。僅僅能在 lambda 表示式中封裝有限的邏輯進去。
- lambda 函式擁有自己的名稱空間,且不能訪問自有引數列表之外或全域性名稱空間裡的引數。
基本語法
lambda [arg1 [,arg2,.....argn]]:expression複製程式碼
示例:
# -*- coding: UTF-8 -*-
sum = lambda num1 , num2 : num1 + num2;
print( sum( 1 , 2 ) )複製程式碼
輸出的結果:
3複製程式碼
注意:儘管 lambda 表示式允許你定義簡單函式,但是它的使用是有限制的。 你只能指定單個表示式,它的值就是最後的返回值。也就是說不能包含其他的語言特性了, 包括多個語句、條件表示式、迭代以及異常處理等等。
匿名函式中,有一個特別需要注意的問題,比如,把上面的例子改一下:
# -*- coding: UTF-8 -*-
num2 = 100
sum1 = lambda num1 : num1 + num2 ;
num2 = 10000
sum2 = lambda num1 : num1 + num2 ;
print( sum1( 1 ) )
print( sum2( 1 ) )複製程式碼
你會認為輸出什麼呢?第一個輸出是 101,第二個是 10001,結果不是的,輸出的結果是這樣:
10001
10001複製程式碼
這主要在於 lambda 表示式中的 num2 是一個自由變數,在執行時繫結值,而不是定義時就繫結,這跟函式的預設值引數定義是不同的。所以建議還是遇到這種情況還是使用第一種解法。