Python :: 定義函式

文章推薦指數: 80 %
投票人數:10人

def 函式. 這時可以定義函式來封裝程式片段,將流程中引用不同數值或變數的部份設計為參數,例如 ... OPENHOME.CC Python |起步走 Hello,Python 簡介模組 IO/格式/編碼 |內建型態 數值 字串 清單 集合 字典 tuple |基本運算 變數 算術運算 比較、指定、邏輯運算 位元運算 |流程語法 if分支判斷 while迴圈、for迭代 forComprehension 初試match/case |函式入門 定義函式 一級函式、lambda運算式 初探變數範圍 yield產生器 |封裝 類別入門 屬性與方法 屬性名稱空間 特殊方法 callable物件 |繼承 共同行為與isa 使用enum列舉 多重繼承與Mixin |例外處理 使用try、except 例外繼承架構 raise例外 使用else、finally 使用withas 使用assert |模組/套件 管理模組名稱 模組路徑 使用套件 |metaprogramming __slots__、__abstractmethods__、__init_subclass__ __getattribute__、__getattr__、__setattr__、__delattr__ 裝飾器 描述器 type類別 metaclass super與mro GitHub Twitter Facebook LinkedIn 2DDesigns 3DDesigns Tags BuiltwithbyHugo HOME> Python> 函式入門> 定義函式 def函式 參數預設值 關鍵字參數 引數拆解 限定位置參數、關鍵字參數 unpacking 定義函式 April19,2022 當開始為了重用某個流程,而複製、貼上、修改變數名稱時,或者發現到兩個或多個程式片段極為類似,只有當中幾個計算用到的數值或變數不同時,就可以考慮將那些片段定義函式。

例如發現到程式中… #其他程式片段... max1=aifa>belseb #其他程式片段... max2=xifx>yelsey #其他程式片段... def函式 這時可以定義函式來封裝程式片段,將流程中引用不同數值或變數的部份設計為參數,例如: defmax(num1,num2): returnnum1ifnum1>num2elsenum2 定義函式時要使用def關鍵字,max是函式名稱,num1、num2是參數名稱,如果要傳回值可以使用return,如果函式執行完畢但沒有使用return傳回值,或者使用了return結束函式但沒有指定傳回值,預設會傳回None。

這麼一來,原先的程式片段就可以修改為: max1=max(a,b) #其他程式片段... max2=max(x,y) #其他程式片段... 函式是一種抽象,對流程的抽象,在定義了max函式之後,客戶端對求最大值的流程,被抽象為max(x,y)這樣的函式呼叫,求值流程實作被隱藏了起來。

在Python中,函式中還可以定義函式,稱為區域函式(Localfunction),可以使用區域函式將某函式中的演算,組織為更小單元,例如,在選擇排序的實作時,每次會從未排序部份,選擇一個最小值放到已排序部份之後,在底下的範例中,尋找最小值的索引時,就以區域函式的方式實作: importsys defsele_sort(number): #找出未排序中最小值 defmin_index(left,right): ifright==len(number): returnleft elifnumber[right]>>defsum(a,b): ...returna+b ... >>>defsum(a,b,c): ...returna+b+c ... >>>sum(1,2) Traceback(mostrecentcalllast): File"",line1,in TypeError:sum()missing1requiredpositionalargument:'c' >>> 參數預設值 雖然不支援函式重載的實作,不過Python可以使用預設引數,有限度地模仿函式重載。

例如: defaccount(name,number,balance=100): return{'name':name,'number':number,'balance':balance} #顯示{'name':'Justin','balance':100,'number':'123-4567'} print(account('Justin','123-4567')) #顯示{'name':'Monica','balance':1000,'number':'765-4321'} print(account('Monica','765-4321',1000)) 使用參數預設值時,必須小心指定了可變動物件時的一個陷阱,Python在執行到def時,就會依定義建立了相關的資源。

來看看下面會有什麼問題? >>>defprepend(elem,lt=[]): ...lt.insert(0,elem) ...returnlt ... >>>prepend(10) [10] >>>prepend(10,[20,30,40]) [10,20,30,40] >>>prepend(20) [20,10] >>> 在上例中,lt預設值設定為[],由於def是個陳述,執行到def的函式定義時,就建立了[],而這個list物件會一直存在,如果沒有指定lt時,使用的就會一直是一開始指定的list物件,也因此,隨著每次呼叫都不指定lt的值,你前置的目標list,都是同一個list。

想要避免這樣的問題,可以將prepend的lt參數預設值設為None,並在函式中指定真正的預設值。

例如: >>>defprepend(elem,lt=None): ...rlt=ltifltelse[] ...rlt.insert(0,elem) ...returnrlt ... >>>prepend(10) [10] >>>prepend(10,[20,30,40]) [10,20,30,40] >>>prepend(20) [20] >>> 在上面的prepend函式中,在lt為None時,使用[]建立新的list實例,這樣就不會有之前的問題。

預設引數在執行到def的函式定義時就固定了,因此不只是指定[]為預設引數會有問題,如果你希望每次函式呼叫時,都要新預設值的需求,就別使用預設引數。

例如: >>>fromdatetimeimportdatetime >>>deflog(msg,time=datetime.now()): ...pring(time,msg) ...print(time,msg) ... >>>log('msg1') 2022-04-2111:15:35.536105msg1 >>>log('msg2') 2022-04-2111:15:35.536105msg2 >>> 在上例中可以看到,time的值始終是第一次呼叫時產生的值,如下撰寫才能避免這個問題: >>>deflog(msg,time=None): ...t=timeiftimeelsedatetime.now() ...print(t,msg) ... >>>log('msg1') 2022-04-2111:18:36.814947msg1 >>>log('msg2') 2022-04-2111:18:39.496919msg2 >>> 關鍵字參數 事實上,在呼叫函式時,並不一定要依參數宣告順序來傳入引數,而可以指定參數名稱來設定其引數值,稱為關鍵字參數。

例如: defaccount(name,number,balance): return{'name':name,'number':number,'balance':balance} #顯示{'name':'Monica','balance':1000,'number':'765-4321'} print(account(balance=1000,name='Monica',number='765-4321')) 引數拆解 如果有個函式擁有固定參數,而你有個序列,像是list、tuple,只要在傳入時加上*,則list或tuple中各元素就會自動拆解給各參數。

例如: defaccount(name,number,balance): return{'name':name,'number':number,'balance':balance} #顯示{'name':'Justin','balance':1000,'number':'123-4567'} print(account(*('Justin','123-4567',1000))) 像sum這種加總數字的函式,事先無法預期要傳入的引數個數,可以在定義函式的參數時使用*,表示該參數接受不定長度引數。

例如: defsum(*numbers): total=0 fornumberinnumbers: total+=number returntotal print(sum(1,2))#顯示3 print(sum(1,2,3))#顯示6 print(sum(1,2,3,4))#顯示10 傳入函式的引數,會被收集在一個tuple中,再設定給numbers參數,這適用於參數個數不固定,而且會循序迭代處理參數的場合。

如果有個dict,打算依鍵名稱,指定給對應的參數名稱,可以在dict前加上**,這樣dict中各對鍵值,就會自動拆解給各參數。

例如: defaccount(name,number,balance): return{'name':name,'number':number,'balance':balance} params={'name':'Justin','number':'123-4567','balance':1000} #顯示{'name':'Justin','balance':1000,'number':'123-4567'} print(account(**params)) 如果參數個數越來越多,而且每個參數名稱皆有其意義,像是defajax(url,method,contents,datatype,accept,headers,username,password),這樣的函式定義不但醜陋,呼叫時也很麻煩,單純只搭配關鍵字參數或預設引數,也不見得能改善多少,將來若因需求而必須增減參數,也會影響函式的呼叫者,因為改變參數個數,就是在改變函式簽署(Signature),也就是函式的外觀,這勢必得逐一修改影響到的程式,造成未來程式擴充時的麻煩。

這個時候,可以試著使用**來定義參數,讓指定的關鍵字參數收集為一個dict。

例如: defajax(url,**user_settings): settings={ 'method':user_settings.get('method','GET'), 'contents':user_settings.get('contents',''), 'datatype':user_settings.get('datatype','text/plain'), #其他設定... } print('請求{}'.format(url)) print('設定{}'.format(settings)) ajax('https://openhome.cc',method='POST',contents='book=python') my_settings={'method':'POST','contents':'book=python'} ajax('https://openhome.cc',**my_settings) 像這樣定義函式就顯得優雅許多,呼叫函式時可使用關鍵字參數,在函式內部也可實現預設引數的效果,這樣的設計在未來程式擴充時比較有利,因為若需增減參數,只需修改函式的內部實作,不用變動函式簽署,函式的呼叫者不會受到影響。

在上面的函式定義中是假設,url為每次呼叫時必須指定的參數,而其他參數可由使用者自行決定是否指定,如果已經有個dict想作為引數,也可以ajax('https://openhome.cc',**my_settings)這樣使用**進行拆解。

可以在一個函式中,同時使用*與**設計參數,如果想設計一個函式接受任意引數,就可以加以運用。

例如: >>>defsome(*arg1,**arg2): ...print(arg1) ...print(arg2) ... >>>some(1,2,3) (1,2,3) {} >>>some(a=1,b=22,c=3) () {'a':1,'c':3,'b':22} >>>some(2,a=1,b=22,c=3) (2,) {'a':1,'c':3,'b':22} >>> 限定位置參數、關鍵字參數 方才的ajax函式設計,url為每次呼叫時必須指定的參數,也許你想要限定呼叫函式時,網址必須作為第一個引數,而且不得使用關鍵字參數的形式來指定,然而目前的ajax函式,無法達到這個需求,例如ajax(method='POST',url='https://openhome.cc')這樣的呼叫方式,也是可以的。

Python3.8新增了Positional-OnlyParameters特性,在定義參數列時可以使用/標示,/前的參數必須依定義的位置呼叫,而且不能採用關鍵字參數的形式來指定,例如: defajax(url,/,**user_settings): settings={ 'method':user_settings.get('method','GET'), 'contents':user_settings.get('contents',''), 'datatype':user_settings.get('datatype','text/plain'), #其他設定... } print('請求{}'.format(url)) print('設定{}'.format(settings)) 若如上定義函式,可以使用ajax('https://openhome.cc',method='POST')呼叫,但是不能用ajax(url='https://openhome.cc',method='POST')或者ajax(method='POST',url='https://openhome.cc')等方式呼叫。

因為/前的參數,不能採用關鍵字參數的形式來指定,對維護會有些幫助,因為你或許會有些參數名稱,不想成為API的一部份,就可以定義在/之前。

相對地,某些參數的值,也許想限定為只能以關鍵字參數形式指定,這時可以在參數列使用*來標示,例如: >>>deffoo(a,b,*,c,d): ...print(a,b,c,d) ... >>>foo(10,20,c=30,d=40) 10203040 >>>foo(10,20,30,40) Traceback(mostrecentcalllast): File"",line1,in TypeError:foo()takes2positionalargumentsbut4weregiven >>> 在以上的範例可以看到,c與d必須使用關鍵字參數形式指定,否則就會引發錯誤。

在定義參數列時,/與*可以併並存,/之後*之前的參數,可以使用位置參數或關鍵字參數形式指定,例如: deffoo(a,b,/,c,d,*,e,f): pass 若如上定義,a、b只能作為位置參數,c、d可以作為位置參數或關鍵字參數,e、f只能作為關鍵字參數。



請為這篇文章評分?