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