每日一python(10): 函式
文章目錄
1 函式的定義和呼叫
函式定義的語法說明:
- 函式程式碼塊以 def 關鍵詞開頭,後接函式識別符號名稱和圓括號 ()。
- 任何傳入引數和自變數必須放在圓括號中間,圓括號之間可以用於定義引數。
- 函式的第一行語句可以選擇性地使用文件字串 — 用於存放函式說明。 函式內容以冒號起始,並且縮排。
- return [表示式] 結束函式,選擇性地返回一個值給呼叫方。不帶表示式的return相當於返回 None。return語句執行時,Python跳出函式並返回到呼叫這個函式的地方。
例如1:使用函式來輸出“hello python”
>>> def hello(): #定義一個hello()函式
print("hello python") #這個函式的功能是輸出hello python
>>> hello() #呼叫函式hello()
hello python
例如2:函式中帶有引數來求矩形面積
>> > def area(width, height): #定義一個函式area,用來計算矩形的面積
return width * height
>>> w = 4
>>> h = 5
>>> print("width = ", w, "height = ", h, "area = ", area(w, h) ) #print語句中呼叫函式area()
width = 4 height = 5 area = 20
>>>
例3:求兩個數中的較大者
def maxTwo(a, b):
if a > b:
print(a)
else:
print(b)
def maxThree(x, y, z):
if x > y:
maxTwo(x, z)
else:
maxTwo(y, z)
maxTwo(5, 8)
maxThree(10, 6, 18)
輸出結果如下:
2 函式引數傳遞
在 python 中,型別屬於物件,變數是沒有型別的,如:
a = [1, 2, 3, 4]
a = “python”
說明:
[1, 2, 3, 4]是List型別,“python”是String型別,而變數a沒有型別,它僅僅是一個物件的引用(即指標),可以指向List型別的物件,也可以指向String型別的物件,當然也可以指向其他型別的物件。
在 python 中,strings, tuples, 和 numbers 是不可更改的物件,而 List,Dict 等是可以修改的物件。在Python的函式傳遞中,對於不可變型別是按值傳遞,對於可變型別是按引用傳遞。
來看兩個例子:
>>> a=1
>>> def fun(a):
a=2
>>> fun(a)
>>> print(a)
列印結果: 1
>>> a = []
>>> def fun(a):
a.append(1)
>>> fun(a)
>>> print(a)
列印結果: [1]
說明: 所有的變數都可以理解成是記憶體中一個物件的“引用”,或者,也可以看似c中void*的感覺。
接下來,通過記憶體地址來觀察一下引用a的變化,如下:
a = 1
print("origin:", id(a)) # 1350574800
def fun(a):
print("fun_in:",id(a)) # 1350574800
a = 2
print("re-point:", id(a),id(2)) # 1350574832 1350574832
print("fun_out:", id(a), id(1)) # 1350574800 1350574800
fun(a)
print(a) # 1
從上面的程式執行結果來看: 在執行完 a = 2 之後,a引用中儲存的值,即記憶體地址發生了變化,由原來1物件的所在地址變成了2這個實體物件的記憶體地址。
a = []
print("origin:", id(a)) # 56735816
def fun(a):
print("fun_in:", id(a)) # 56735816
a.append(1)
print("fun_out:", id(a)) # 56735816
fun(a)
print(a) # [1]
從上面的程式執行結果來看: a引用儲存的記憶體地址始終沒有發生變化。
注意:
(1) 這裡記住的是型別,是屬於物件的,而不是變數。而物件有兩種:“可更改”(mutable)與“不可更改”(immutable)物件。在python中,strings,tuples,和numbers是不可更改的物件,而list,dict等則是可以修改的物件。(這就是這個問題的重點)
(2) 當一個引用傳遞給函式的時候,函式會自動複製一份引用,這個函式裡的引用和外邊的引用就沒有任何關係了。所以在第一個例子裡函式把引用指向了一個不可變物件,當函式返回的時候,外面的引用沒有發生任何變化;而第二個例子就不一樣了,函式內的引用指向的是可變物件,對它的操作就與定位了指標地址一樣,在記憶體裡進行修改。
(3) Python不允許程式設計師選擇採用傳值還是傳引用。Python引數傳遞採用的肯定是“傳物件引用”的方式。實際上,這種方式相當於傳值和傳引用的一種綜合。如果函式收到的是一個可變物件(比如字典或者列表)的引用,就能修改物件的原始值——相當於通過“傳引用”來傳遞物件。如果函式收到的是一個不可變物件(比如數字、字元或者元組)的引用,就不能直接修改原始物件——相當於通過“傳值”來傳遞物件。
(4) 當人們複製列表或字典時,就複製了物件列表的引用。如果改變引用的值,則就修改了原始的引數。
3 函式的引數
3.1 位置引數
例:設計一個函式求某個數的平方數(x2)
>>> def power(x):
return x * x
對於power(x) 函式,引數x 就是一個位置引數。
當我們呼叫power 函式時,必須傳入有且僅有的一個引數x :
>>> power(5)
25
>>> power(15)
225
現在,如果我們要計算x3怎麼辦?可以再定義一個power3 函式,但是如果要計算x4、x5……怎麼辦?我們不可能定義無限多個函式。
你也許想到了,可以把power(x) 修改為power(x, n) ,用來計算x的n次方,如下:
>>> def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
對於這個修改後的power(x, n) 函式,可以計算任意n次方:
>>> power(5, 2)
25
>>> power(5, 3)
125
說明: 修改後的power(x, n) 函式有兩個引數: x 和 n ,這兩個引數都是位置引數,呼叫函式時,傳入的兩個值,按照位置順序依次賦給引數x和n 。
3.2 預設引數
新的power(x, n) 函式定義沒有問題,但是,舊的呼叫程式碼失敗了,原因是我們增加了一個引數,導致舊的程式碼因為缺少一個引數而無法正常呼叫,如下:
>>> power(5)
Traceback (most recent call last):
File "<pyshell#25>", line 1, in <module>
power(5)
TypeError: power() missing 1 required positional argument: 'n'
錯誤提示:呼叫power()函式缺少一個位置引數n。
這個時候預設引數就可以派上用場了,由於我們經常計算x的平方,所以,完全可以把第二個引數n的預設值設定為2,如下:
>>> def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
這樣,當我們呼叫power(5) 時,相當於呼叫power(5, 2) ,如下:
>>> power(5)
25
>>> power(5, 2)
25
而對於n > 2 的其他情況,就必須明確地傳入n,比如power(5, 3),如下:
>>> power(5, 3)
125
從上面的例子可以看出,預設引數可以簡化函式的呼叫。
設定預設引數時,有幾點要注意:
一是: 必選引數在前,預設引數在後,否則Python的直譯器會報錯(思考一下為什麼預設引數不能放在必選引數前面);
二是: 如何設定預設引數。
當函式有多個引數時,把變化大的引數放前面,變化小的引數放後面。變化小的引數就可以作為預設引數。
使用預設引數有什麼好處?最大的好處是能降低呼叫函式的難度。
下面針對預設引數,看下面的一個問題,有如下一個函式:
def add_end(L=[]):
L.append('Python')
return L
add_end()
add_end()
想一想,返回結果是什麼???
想一想,為什麼返回結果是上面的樣子???這是因為:
Python函式在定義的時候,預設引數L的值就被計算出來了,即 [],因為預設引數L也是一個變數,它指向物件是 [](是可變的),每次呼叫該函式,如果改變了L的內容,則下次呼叫時,預設引數的內容就變了,不再是函式定義時的 []了。
因此,定義預設引數要牢記一點:預設引數必須指向不變物件!
要修改上面的例子,我們可以用None這個不變物件來實現:
def add_end(L=None):
if L is None:
L = []
L.append('Python')
return L
add_end()
add_end()
這次的返回結果就是我們預期的結果了,如下:
3.3 可變引數
在Python函式中,還可以定義可變引數。顧名思義,可變引數就是傳入的引數個數是可變的,可以是1個、2個到任意個,還可以是0個。
我們以數學題為例子,給定一組數字a,b,c……,請計算a + b + c + ……。
要定義出這個函式,我們必須確定輸入的引數。由於引數個數不確定,我們首先想到可以把a,b,c……作為一個list或tuple傳進來,這樣,函式可以定義如下:
>>> def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n
return sum
但是呼叫的時候,需要先組裝出一個list或tuple:
>>> calc([1, 2, 3])
6
>>> calc([1, 3, 5, 7, 9])
25
而如果我們利用可變引數,呼叫函式的方式就可以寫成如下形式:(不必list或tuple)
>>> calc(1, 2, 3)
6
>>> calc(1, 3, 5, 7, 9)
25
所以,這裡我們需要將calc函式的引數改成可變引數,如下:
>>> def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n
return sum
我們看到:定義可變引數和定義一個list或tuple引數相比,僅僅在引數前面加了一個*號。在函式內部,引數numbers接收到的是一個list或者tuple,因此,函式程式碼完全不變。但是,呼叫該函式時,可以傳入任意個引數,包括0個引數:
>>> calc(2, 4, 6, 8, 10)
30
>>> calc()
0
如果已經有一個list或者tuple,要呼叫一個可變引數怎麼辦?可以這樣做:
>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
6
這種寫法當然是可行的,問題是太繁瑣,所以Python允許你在list或tuple前面加一個 * 號,把list或tuple的元素變成可變引數傳進去:
>>> calc(*nums)
6
注意: 這裡 *nums 表示把nums這個list的所有元素作為可變引數傳進去。這種寫法相當有用,而且很常見。
3.4 關鍵字引數
可變引數允許你傳入0個或任意個引數,這些可變引數在函式呼叫時自動組裝為一個list或者tuple。而關鍵字引數允許你傳入0個或任意個含引數名的引數,這些關鍵字引數在函式內部自動組裝為一個dict。
例如:
>>> def person(name, age, **key):
print("name:", name, "age:", age, "other:", key)
函式person除了必選引數name和age外,還接受關鍵字引數key 。在呼叫該函式時,可以只傳入必選引數:
>>> person('Tom', 30)
name: Tom age: 30 other: {}
也可以傳入任意個數的關鍵字引數:
>>> person("Bob", 28, city = "shanghai")
name: Bob age: 28 other: {'city': 'shanghai'}
>>> person('jonana', 32, gender = 'M', job = 'Teacher', city = "Shanghai")
name: jonana age: 32 other: {'gender': 'M', 'city': 'Shanghai', 'job': 'Teacher'}
關鍵字引數有什麼用?它可以擴充套件函式的功能。比如,在person 函式裡,我們保證能接收到name和age這兩個引數,但是,如果呼叫者願意提供更多的引數,我們也能收到。試想你正在做一個使用者註冊的功能,除了使用者名稱和年齡是必填項外,其他都是可選項,利用關鍵字引數來定義這個函式就能滿足註冊的需求。
和可變引數類似,也可以先組裝出一個dict,然後,把該dict轉換為關鍵字引數傳進去:
>>> extra = {'city':'Shanghai', 'job':'Engineer'}
>>> person('jack', 24, city = extra['city'], job = extra['job'])
name: jack age: 24 other: {'city': 'Shanghai', 'job': 'Engineer'}
當然,上面複雜的呼叫可以用簡化的寫法,如下:
>>> extra = {'city':'Shanghai', 'job':'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Shanghai', 'job': 'Engineer'}
說明: **extra
表示把extra這個dict的所有key-value用關鍵字引數傳入到函式的**key
引數,key將獲得一個dict,注意key獲得的dict是extra的一份拷貝,對key的改動不會影響到函式外的extra。
3.5 命名關鍵字引數
對於關鍵字引數,函式的呼叫者可以傳入任意不受限制的關鍵字引數。至於到底傳入了哪些,就需要在函式內部通過key檢查。
仍以person() 函式為例,我們希望檢查是否有city和job引數:
>>> def person(name, age, **key):
if 'city' in key:
# 存在city引數
pass
if 'job' in key:
# 存在job引數
pass
print('name:', name, 'age:', age, 'other:', key)
但是呼叫者仍可以傳入不受限制的關鍵字引數:
>>> person('Jack', 24, city = 'Shanghai', addr = 'Puong')
name: Jack age: 24 other: {'addr': 'Puong', 'city': 'Shanghai'}
如果要限制關鍵字引數的名字,就可以用命名關鍵字引數,例如,只接收city和job作為關鍵字引數。這種方式定義的函式如下:
>>> def person(name, age, *, city, job):
print(name, age, city, job)
我們看到:與關鍵字引數**key不同,命名關鍵字引數需要一個特殊分隔符*, *後面的引數被視為命名關鍵字引數。
呼叫方式如下:
>>> person('Jack', 24, city='Shanghai', job='Engineer')
Jack 24 Shanghai Engineer
注意: 命名關鍵字引數必須傳入引數名,這和位置引數不同。如果沒有傳入引數名,呼叫將報錯,如下:
>>> person('Jack', 24, 'Shanghai', 'Engineer')
Traceback (most recent call last):
File "<pyshell#144>", line 1, in <module>
person('Jack', 24, 'Shanghai', 'Engineer')
TypeError: person() takes 2 positional arguments but 4 were given
說明: 由於呼叫時缺少引數名city 和job ,Python直譯器把這4個引數均視為位置引數,但person() 函式僅接受2個位置引數。
另外,命名關鍵字引數可以有預設值,從而簡化呼叫,例如:
>>> def person(name, age, *, city = 'Shanghai', job):
print(name, age, city, job)
這裡,由於命名關鍵字引數city 具有預設值,呼叫時,可不傳入city 引數:
>>> person('Jack', 24, job = 'Engineer')
Jack 24 Shanghai Engineer
注意: 使用命名關鍵字引數時,要特別注意, * 不是引數,而是特殊分隔符。如果缺少 * ,Python直譯器將無法識別位置引數和命名關鍵字引數。