前言
前天創(chuàng)了個(gè) Python 微信討論群着倾,以為沒人進(jìn)的拾酝,哈哈燕少,想不到還真有小伙伴進(jìn)群學(xué)習(xí)討論。如果想進(jìn)群蒿囤,可以加我微信: androidwed 客们,拉進(jìn)群,就不貼微信群二維碼了材诽,一是會(huì)失效底挫,二影響文章。
目錄
一脸侥、Python 自定義函數(shù)的基本步驟
函數(shù)是組織好的,可重復(fù)使用的睁枕,用來實(shí)現(xiàn)單一官边,或相關(guān)聯(lián)功能的代碼段。
自定義函數(shù)外遇,基本有以下規(guī)則步驟:
- 函數(shù)代碼塊以 def 關(guān)鍵詞開頭注簿,后接函數(shù)標(biāo)識(shí)符名稱和圓括號(hào)()
- 任何傳入?yún)?shù)和自變量必須放在圓括號(hào)中間。圓括號(hào)之間可以用于定義參數(shù)
- 函數(shù)的第一行語句可以選擇性地使用文檔字符串(用于存放函數(shù)說明)
- 函數(shù)內(nèi)容以冒號(hào)起始跳仿,并且縮進(jìn)
- return [表達(dá)式] 結(jié)束函數(shù)诡渴,選擇性地返回一個(gè)值給調(diào)用方。不帶表達(dá)式的 return 相當(dāng)于返回 None菲语。
語法示例:
def functionname( parameters ):
"函數(shù)_文檔字符串"
function_suite
return [expression]
實(shí)例:
- def 定義一個(gè)函數(shù)妄辩,給定一個(gè)函數(shù)名 sum
- 聲明兩個(gè)參數(shù) num1 和 num2
- 函數(shù)的第一行語句進(jìn)行函數(shù)說明:兩數(shù)之和
- 最終 return 語句結(jié)束函數(shù)惑灵,并返回兩數(shù)之和
def sum(num1,num2):
"兩數(shù)之和"
return num1+num2
# 調(diào)用函數(shù)
print(sum(5,6))
輸出結(jié)果:
11
二、函數(shù)傳值問題
先看一個(gè)例子:
# -*- coding: UTF-8 -*-
def chagne_number( b ):
b = 1000
b = 1
chagne_number(b)
print( b )
最后輸出的結(jié)果為:
1
這里可能有些人會(huì)有疑問眼耀,為啥不是通過函數(shù)chagne_number
更改了 b
的值嗎泣棋?為啥沒有變化,輸出的結(jié)果還是 1 畔塔,這個(gè)問題很多編程語言都會(huì)講到潭辈,原理解釋也是差不多的。
這里主要是函數(shù)參數(shù)的傳遞中澈吨,傳遞的是類型對(duì)象把敢,之前也介紹了 Python 中基本的數(shù)據(jù)類型等。而這些類型對(duì)象可以分為可更改類型和不可更改的類型
在 Python 中谅辣,字符串修赞,整形,浮點(diǎn)型桑阶,tuple 是不可更改的對(duì)象柏副,而 list , dict 等是可以更改的對(duì)象蚣录。
例如:
不可更改的類型:變量賦值 a = 1
割择,其實(shí)就是生成一個(gè)整形對(duì)象 1 ,然后變量 a 指向 1萎河,當(dāng) a = 1000
其實(shí)就是再生成一個(gè)整形對(duì)象 1000荔泳,然后改變 a 的指向,不再指向整形對(duì)象 1 虐杯,而是指向 1000玛歌,最后 1 會(huì)被丟棄
可更改的類型:變量賦值 a = [1,2,3,4,5,6]
,就是生成一個(gè)對(duì)象 list 擎椰,list 里面有 6 個(gè)元素支子,而變量 a 指向 list ,a[2] = 5
則是將 list a 的第三個(gè)元素值更改,這里跟上面是不同的达舒,并不是將 a 重新指向值朋,而是直接修改 list 中的元素值。
這也將影響到函數(shù)中參數(shù)的傳遞了:
不可更改的類型:類似 c++ 的值傳遞休弃,如 整數(shù)吞歼、字符串、元組塔猾。如fun(a)篙骡,傳遞的只是 a 的值,沒有影響 a 對(duì)象本身。比如在 fun(a)內(nèi)部修改 a 的值糯俗,只是修改另一個(gè)復(fù)制的對(duì)象尿褪,不會(huì)影響 a 本身。
可更改的類型:類似 c++ 的引用傳遞得湘,如 列表杖玲,字典。如 fun(a)淘正,則是將 a 真正的傳過去摆马,修改后 fun 外部的 a 也會(huì)受影響
因此,在一開始的例子中鸿吆,b = 1
,創(chuàng)建了一個(gè)整形對(duì)象 1 囤采,變量 b 指向了這個(gè)對(duì)象,然后通過函數(shù) chagne_number 時(shí)惩淳,按傳值的方式復(fù)制了變量 b 蕉毯,傳遞的只是 b 的值,并沒有影響到 b 的本身思犁。具體可以看下修改后的實(shí)例代虾,通過打印的結(jié)果更好的理解。
# -*- 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 ) )
打印的結(jié)果:
函數(shù)中一開始 b 的值:1
函數(shù)中 b 賦值后的值:1000
最后輸出 b 的值:1
當(dāng)然激蹲,如果參數(shù)中的是可更改的類型棉磨,那么調(diào)用了這個(gè)函數(shù)后,原來的值也會(huì)被更改托呕,具體實(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 ) )
輸出的結(jié)果:
函數(shù)中一開始 b 的值:[1, 2, 3, 4, 5]
函數(shù)中 b 賦值后的值:[1, 2, 3, 4, 5, 1000]
最后輸出 b 的值:[1, 2, 3, 4, 5, 1000]
三含蓉、函數(shù)返回值
通過上面的學(xué)習(xí),可以知道通過 return [表達(dá)式] 語句用于退出函數(shù)项郊,選擇性地向調(diào)用方返回一個(gè)表達(dá)式。不帶參數(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ù)類型錯(cuò)誤')
return num1+num2
print(sum(1,2))
返回結(jié)果:
3
這個(gè)示例着降,還通過內(nèi)置函數(shù)isinstance()
進(jìn)行數(shù)據(jù)類型檢查,檢查調(diào)用函數(shù)時(shí)參數(shù)是否是整形和浮點(diǎn)型拗军。如果參數(shù)類型不對(duì)任洞,會(huì)報(bào)錯(cuò),提示 參數(shù)類型錯(cuò)誤
,如圖:
當(dāng)然发侵,函數(shù)也可以返回多個(gè)值交掏,具體實(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)
認(rèn)真觀察就可以發(fā)現(xiàn),盡管從第一個(gè)輸出值來看刃鳄,返回了多個(gè)值盅弛,實(shí)際上是先創(chuàng)建了一個(gè)元組然后返回的。回憶一下挪鹏,元組是可以直接用逗號(hào)來創(chuàng)建的见秽,觀察例子中的 ruturn ,可以發(fā)現(xiàn)實(shí)際上我們使用的是逗號(hào)來生成一個(gè)元組讨盒。
四解取、函數(shù)的參數(shù)
1、默認(rèn)值參數(shù)
有時(shí)候返顺,我們自定義的函數(shù)中禀苦,如果調(diào)用的時(shí)候沒有設(shè)置參數(shù),需要給個(gè)默認(rèn)值遂鹊,這時(shí)候就需要用到默認(rèn)值參數(shù)了伦忠。
# -*- coding: UTF-8 -*-
def print_user_info( name , age , sex = '男' ):
# 打印用戶信息
print('昵稱:{}'.format(name) , end = ' ')
print('年齡:{}'.format(age) , end = ' ')
print('性別:{}'.format(sex))
return;
# 調(diào)用 print_user_info 函數(shù)
print_user_info( '兩點(diǎn)水' , 18 , '女')
print_user_info( '三點(diǎn)水' , 25 )
輸出結(jié)果:
昵稱:兩點(diǎn)水 年齡:18 性別:女
昵稱:三點(diǎn)水 年齡:25 性別:男
可以看到,當(dāng)你設(shè)置了默認(rèn)參數(shù)的時(shí)候稿辙,在調(diào)用函數(shù)的時(shí)候昆码,不傳該參數(shù),就會(huì)使用默認(rèn)值邻储。但是這里需要注意的一點(diǎn)是:只有在形參表末尾的那些參數(shù)可以有默認(rèn)參數(shù)值赋咽,也就是說你不能在聲明函數(shù)形參的時(shí)候,先聲明有默認(rèn)值的形參而后聲明沒有默認(rèn)值的形參吨娜。這是因?yàn)橘x給形參的值是根據(jù)位置而賦值的脓匿。例如,def func(a, b=1) 是有效的宦赠,但是 def func(a=1, b) 是 無效 的陪毡。
默認(rèn)值參數(shù)就這樣結(jié)束了嗎?還沒有的勾扭,細(xì)想一下毡琉,如果參數(shù)中是一個(gè)可修改的容器比如一個(gè) lsit (列表)或者 dict (字典),那么我們使用什么來作為默認(rèn)值呢妙色?我們可以使用 None 作為默認(rèn)值桅滋。就像下面這個(gè)例子一樣:
# 如果 b 是一個(gè) list ,可以使用 None 作為默認(rèn)值
def print_info( a , b = None ):
if b is None :
b=[]
return;
認(rèn)真看下例子身辨,會(huì)不會(huì)有這樣的疑問呢丐谋?在參數(shù)中我們直接 b=[]
不就行了嗎?也就是寫成下面這個(gè)樣子:
def print_info( a , b = [] ):
return;
對(duì)不對(duì)呢煌珊?運(yùn)行一下也沒發(fā)現(xiàn)錯(cuò)誤啊号俐,可以這樣寫嗎?這里需要特別注意的一點(diǎn):默認(rèn)參數(shù)的值是不可變的對(duì)象定庵,比如None吏饿、True踪危、False、數(shù)字或字符串找岖,如果你像上面的那樣操作陨倡,當(dāng)默認(rèn)值在其他地方被修改后你將會(huì)遇到各種麻煩。這些修改會(huì)影響到下次調(diào)用這個(gè)函數(shù)時(shí)的默認(rèn)值许布。
示例如下:
# -*- coding: UTF-8 -*-
def print_info( a , b = [] ):
print(b)
return b ;
result = print_info(1)
result.append('error')
print_info(2)
輸出的結(jié)果:
[]
['error']
認(rèn)真觀察兴革,你會(huì)發(fā)現(xiàn)第二次輸出的值根本不是你想要的,因此切忌不能這樣操作蜜唾。
還有一點(diǎn)杂曲,有時(shí)候我就是不想要默認(rèn)值啊,只是想單單判斷默認(rèn)參數(shù)有沒有值傳遞進(jìn)來袁余,那該怎么辦绢淀?我們可以這樣做:
_no_value =object()
def print_info( a , b = _no_value ):
if b is _no_value :
print('b 沒有賦值')
return;
這里的 object
是python中所有類的基類烧董。 你可以創(chuàng)建 object
類的實(shí)例,但是這些實(shí)例沒什么實(shí)際用處,因?yàn)樗]有任何有用的方法伟众, 也沒有任何實(shí)例數(shù)據(jù)(因?yàn)樗鼪]有任何的實(shí)例字典算灸,你甚至都不能設(shè)置任何屬性值)蛤吓。 你唯一能做的就是測(cè)試同一性充活。也正好利用這個(gè)特性,來判斷是否有值輸入且蓬。
2欣硼、關(guān)鍵字參數(shù)
在 Python 中,可以通過參數(shù)名來給函數(shù)傳遞參數(shù)恶阴,而不用關(guān)心參數(shù)列表定義時(shí)的順序诈胜,這被稱之為關(guān)鍵字參數(shù)。使用關(guān)鍵參數(shù)有兩個(gè)優(yōu)勢(shì) :
一冯事、由于我們不必?fù)?dān)心參數(shù)的順序焦匈,使用函數(shù)變得更加簡(jiǎn)單了。
二桅咆、假設(shè)其他參數(shù)都有默認(rèn)值括授,我們可以只給我們想要的那些參數(shù)賦值
# -*- coding: UTF-8 -*-
def print_user_info( name , age , sex = '男' ):
# 打印用戶信息
print('昵稱:{}'.format(name) , end = ' ')
print('年齡:{}'.format(age) , end = ' ')
print('性別:{}'.format(sex))
return;
# 調(diào)用 print_user_info 函數(shù)
print_user_info( name = '兩點(diǎn)水' ,age = 18 , sex = '女')
print_user_info( name = '兩點(diǎn)水' ,sex = '女', age = 18 )
輸出的值:
昵稱:兩點(diǎn)水 年齡:18 性別:女
昵稱:兩點(diǎn)水 年齡:18 性別:女
3、不定長(zhǎng)參數(shù)
有時(shí)我們?cè)谠O(shè)計(jì)函數(shù)接口的時(shí)候岩饼,可會(huì)需要可變長(zhǎng)的參數(shù)。也就是說薛夜,我們事先無法確定傳入的參數(shù)個(gè)數(shù)籍茧。Python 提供了一種元組的方式來接受沒有直接定義的參數(shù)。這種方式在參數(shù)前邊加星號(hào) *
梯澜。如果在函數(shù)調(diào)用時(shí)沒有指定參數(shù)寞冯,它就是一個(gè)空元組渴析。我們也可以不向函數(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;
# 調(diào)用 print_user_info 函數(shù)
print_user_info( '兩點(diǎn)水' ,18 , '女', '打籃球','打羽毛球','跑步')
輸出的結(jié)果:
昵稱:兩點(diǎn)水 年齡:18 性別:女 愛好:('打籃球', '打羽毛球', '跑步')
通過輸出的結(jié)果可以知道吮龄,*hobby
是可變參數(shù)俭茧,且 hobby其實(shí)就是一個(gè) tuple (元祖)
可變長(zhǎng)參數(shù)也支持關(guān)鍵參數(shù),沒有被定義的關(guān)鍵參數(shù)會(huì)被放到一個(gè)字典里漓帚。這種方式即是在參數(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;
# 調(diào)用 print_user_info 函數(shù)
print_user_info( name = '兩點(diǎn)水' , age = 18 , sex = '女', hobby = ('打籃球','打羽毛球','跑步'))
輸出的結(jié)果:
昵稱:兩點(diǎn)水 年齡:18 性別:女 愛好:{'hobby': ('打籃球', '打羽毛球', '跑步')}
通過對(duì)比上面的例子和這個(gè)例子母债,可以知道,*hobby
是可變參數(shù)尝抖,且 hobby其實(shí)就是一個(gè) tuple (元祖)毡们,**hobby
是關(guān)鍵字參數(shù),且 hobby 就是一個(gè) dict (字典)
4昧辽、只接受關(guān)鍵字參數(shù)
關(guān)鍵字參數(shù)使用起來簡(jiǎn)單衙熔,不容易參數(shù)出錯(cuò),那么有些時(shí)候搅荞,我們定義的函數(shù)希望某些參數(shù)強(qiáng)制使用關(guān)鍵字參數(shù)傳遞红氯,這時(shí)候該怎么辦呢?
將強(qiáng)制關(guān)鍵字參數(shù)放到某個(gè)*
參數(shù)或者單個(gè)*
后面就能達(dá)到這種效果,比如:
# -*- coding: UTF-8 -*-
def print_user_info( name , *, age , sex = '男' ):
# 打印用戶信息
print('昵稱:{}'.format(name) , end = ' ')
print('年齡:{}'.format(age) , end = ' ')
print('性別:{}'.format(sex))
return;
# 調(diào)用 print_user_info 函數(shù)
print_user_info( name = '兩點(diǎn)水' ,age = 18 , sex = '女' )
# 這種寫法會(huì)報(bào)錯(cuò)咕痛,因?yàn)?age 痢甘,sex 這兩個(gè)參數(shù)強(qiáng)制使用關(guān)鍵字參數(shù)
#print_user_info( '兩點(diǎn)水' , 18 , '女' )
print_user_info('兩點(diǎn)水',age='22',sex='男')
通過例子可以看,如果 age
, sex
不適用關(guān)鍵字參數(shù)是會(huì)報(bào)錯(cuò)的暇检。
很多情況下产阱,使用強(qiáng)制關(guān)鍵字參數(shù)會(huì)比使用位置參數(shù)表意更加清晰,程序也更加具有可讀性块仆。使用強(qiáng)制關(guān)鍵字參數(shù)也會(huì)比使用 **kw
參數(shù)更好且強(qiáng)制關(guān)鍵字參數(shù)在一些更高級(jí)場(chǎng)合同樣也很有用构蹬。
五、匿名函數(shù)
有沒有想過定義一個(gè)很短的回調(diào)函數(shù)悔据,但又不想用 def
的形式去寫一個(gè)那么長(zhǎng)的函數(shù)庄敛,那么有沒有快捷方式呢?答案是有的科汗。
python 使用 lambda 來創(chuàng)建匿名函數(shù)藻烤,也就是不再使用 def 語句這樣標(biāo)準(zhǔn)的形式定義一個(gè)函數(shù)。
匿名函數(shù)主要有以下特點(diǎn):
- lambda 只是一個(gè)表達(dá)式头滔,函數(shù)體比 def 簡(jiǎn)單很多怖亭。
- lambda 的主體是一個(gè)表達(dá)式,而不是一個(gè)代碼塊坤检。僅僅能在 lambda 表達(dá)式中封裝有限的邏輯進(jìn)去兴猩。
- lambda 函數(shù)擁有自己的命名空間,且不能訪問自有參數(shù)列表之外或全局命名空間里的參數(shù)早歇。
基本語法
lambda [arg1 [,arg2,.....argn]]:expression
示例:
# -*- coding: UTF-8 -*-
sum = lambda num1 , num2 : num1 + num2;
print( sum( 1 , 2 ) )
輸出的結(jié)果:
3
注意:盡管 lambda 表達(dá)式允許你定義簡(jiǎn)單函數(shù)倾芝,但是它的使用是有限制的讨勤。 你只能指定單個(gè)表達(dá)式,它的值就是最后的返回值晨另。也就是說不能包含其他的語言特性了潭千, 包括多個(gè)語句、條件表達(dá)式借尿、迭代以及異常處理等等刨晴。
匿名函數(shù)中,有一個(gè)特別需要注意的問題垛玻,比如割捅,把上面的例子改一下:
# -*- coding: UTF-8 -*-
num2 = 100
sum1 = lambda num1 : num1 + num2 ;
num2 = 10000
sum2 = lambda num1 : num1 + num2 ;
print( sum1( 1 ) )
print( sum2( 1 ) )
你會(huì)認(rèn)為輸出什么呢?第一個(gè)輸出是 101帚桩,第二個(gè)是 10001亿驾,結(jié)果不是的,輸出的結(jié)果是這樣:
10001
10001
這主要在于 lambda 表達(dá)式中的 num2 是一個(gè)自由變量账嚎,在運(yùn)行時(shí)綁定值莫瞬,而不是定義時(shí)就綁定,這跟函數(shù)的默認(rèn)值參數(shù)定義是不同的郭蕉。所以建議還是遇到這種情況還是使用第一種解法疼邀。