Python :: 定義函式
文章推薦指數: 80 %
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]
例如:
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"
在定義參數列時,/與*可以併並存,/之後*之前的參數,可以使用位置參數或關鍵字參數形式指定,例如:
deffoo(a,b,/,c,d,*,e,f):
pass
若如上定義,a、b只能作為位置參數,c、d可以作為位置參數或關鍵字參數,e、f只能作為關鍵字參數。
延伸文章資訊
- 1Python中的函數| 學呀- Python | def、程式設計、方法
Python中的函數在上個章節中,我們提到了一些關於函數的基本概念—函數是一個自定義的運算符號,用來處理較為複雜的程式運算。函數就像一臺機器,你對它是輸入一些數值 ...
- 2[Python教學]5個必知的Python Function觀念整理
今天要來教大家如何建構自己的 Python函式(Function),就是能夠讓你的程式 ... 預設值參數(Default Argument):在函式定義的參數中,將可以選擇性傳入 ...
- 3Python/函數- 維基教科書,自由的教學讀本 - Wikibooks
函數定義編輯. Python函數定義的通常格式為:. def 函数名(参数列表): #函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明 函数体 #return [表达式] ...
- 4Python入門(二)函數基礎 - 數據分析那些事- Medium
函數(Functions)是指可重複使用的程式片段。它們允許你為某個程式碼塊賦予名字,允許你透過這一特殊的名字在你的程式任何地方來運行代碼塊 ...
- 5Python 入門筆記:函式基礎,什麼是函式?什麼是回傳值return?
定義函式