1. 程式人生 > >每日一python(10): 函式

每日一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直譯器將無法識別位置引數和命名關鍵字引數。