1. 程式人生 > 其它 >Python學習筆記:函式和lambda表示式

Python學習筆記:函式和lambda表示式

第五章 函式與lambda表示式

函式是執行特定任務的一段程式碼,程式通過將一段程式碼定義成函式,併為該函式指定一個函式名,這樣即可在需要的時候多次呼叫這段程式碼。因此,函式是程式碼複用的重要手段。

函式入門

理解函式

將實現特定功能的程式碼定義成一個函式,每次當程式需要實現該功能時,只要執行(呼叫)該函式即可。
所謂函式,就是指為一段實現特定功能的程式碼“取”一個名字,以後即可通過該名字來執行(呼叫)該函式。
通常,函式可以接收零個或多個引數,也可以返回零個或多個值。
從函式使用者角度看,函式就像一個“黑匣子”,程式將零個或多個引數傳入這個“黑匣子”,該“黑匣子”經過一番計算即可返回零個或多個值。

從函式定義者(實現函式的人)的角度來看,其至少需要想清楚以下3點:

  • 函式需要幾個關鍵的需要動態變化的資料,這些資料應該被定義成函式的引數。
  • 函式需要傳出幾個重要的資料(就是呼叫該函式的人希望得到的資料),這些資料應該被定義成返回值。
  • 函式的內部實現過程。

定義函式和呼叫函式

定義函式的語法格式如下:

def 函式名(形參列表):
    // 由零條到多條可執行語句組成的函式
    [return [返回值]]

python宣告函式必須使用def關鍵字,對函式語法格式的詳細說明如下:

  • 函式名:從語法角度看,函式名只要是一個合法的標誌符即可;從程式的可讀性角度看,函式名應該由一個或多個有意義的單詞連綴而成,每個單詞的字母全部小寫,單詞與單詞之間使用下劃線分割。
  • 形參列表:用於定義該函式可以接收的形參。形參列表由多個形參名組成,多個形參名之間有英文逗號(,)隔開。一旦在定義函式時指定了形參列表,呼叫該函式時就必須傳入對應的形參值 --- 誰呼叫,誰負責為形參賦值。
## 定義一個函式,宣告2個形參
#def my_max(x, y) : 
#	# 定義一個變數z,該變數等於x、y中較大的值
#	z = x if x > y else y
#	# 返回變數z的值
#	return z
def my_max(x, y) : 
	# 返回一個表示式
	return x if x > y else y

# 定義一個函式,宣告一個形參
def say_hi(name) :
	print("===正在執行say_hi()函式===")
	return name + ",您好!"
a = 6
b = 9
# 呼叫my_max()函式,將函式返回值賦值給result變數
result = my_max(a , b) # ①
print("result:", result)
# 呼叫say_hi()函式,直接輸出函式的返回值
print(say_hi("孫悟空")) # ②

輸出結果:

result: 9
===正在執行say_hi()函式===
孫悟空,您好!

在函式體中使用return語句可以顯示地返回一個值,return語句返回的值既可以是有值的變數,也可是一個表示式。

為函式提供文件

為函式編寫說明文件 --- 只要把一段字串放在函式宣告之後、函式體之前,這段字串將被作為函式的部分,這個文件就是函式的說明文件。
程式即可通過help()函式檢視函式的說明文件,也可通過函式的_doc_屬性訪問函式的說明文件。示例:

def my_max(x, y) : 
    '''
    獲取兩個數值之間較大數的函式。

    my_max(x, y)
        返回x、y兩個引數之間較大的那個
    '''
    # 定義一個變數z,該變數等於x、y中較大的值
    z = x if x > y else y
    # 返回變數z的值
    return z
# 使用help()函式檢視my_max的幫助文件
help(my_max)
print("====================================")
print(my_max.__doc__)

輸出結果:

Help on function my_max in module __main__:

my_max(x, y)
    獲取兩個數值之間較大數的函式。
    
    my_max(x, y)
        返回x、y兩個引數之間較大的那個

====================================

    獲取兩個數值之間較大數的函式。

    my_max(x, y)
        返回x、y兩個引數之間較大的那個

上面程式使用多行字串的語法為my_max()函式編寫了說明文件,接下來程式即可通過help()函式檢視該函式的說明文件,也可通過 _doc _屬性訪問該函式的說明文件。

多個返回值

如果程式需要有多個返回值,則即可將多個值包裝成列表之後返回,也可直接返回多個值。如果直接返回多個值,python會自動將多個值封裝成元組。

def sum_and_avg(list):
    sum = 0
    count = 0
    for e in list:
        # 如果元素e是數值
        if isinstance(e, int) or isinstance(e, float):
            count += 1
            sum += e
    return sum, sum / count
my_list = [20, 15, 2.8, 'a', 35, 5.9, -1.8]
# 獲取sum_and_avg函式返回的多個值,多個返回值被封裝成元組
tp = sum_and_avg(my_list) #①
print(tp)
# 使用序列解包來獲取多個返回值
s, avg = sum_and_avg(my_list) #②
print(s)
print(avg)

輸出結果:

(76.9, 12.816666666666668)
76.9
12.816666666666668

遞迴函式

在一個函式體內呼叫它自身,被稱為函式遞迴。函式遞迴包含了一種隱式的迴圈,它會重複執行某段程式碼,但這種迴圈執行無需迴圈控制。
當一個函式不斷地呼叫它自身時,必須在某個時刻函式的返回值是確定的,即不再呼叫它自身;否則,這種遞迴就變成了無窮遞迴,類似於死迴圈。因此,在定義遞迴函式時有一條最重要的規定:遞迴一定要向已知方向進行。
總之,只要在一個函式的函式體內呼叫了函式自身,就是函式遞迴。遞迴一定要向已知方向進行。

def fn(n) :
	if n == 0 :
		return 1
	elif n == 1 :
		return 4
	else :
		# 函式中呼叫它自身,就是函式遞迴
		return 2 * fn(n - 1) + fn(n - 2)
# 輸出fn(10)的結果
print("fn(10)的結果是:", fn(10))

輸出結果:

fn(10)的結果是: 10497

函式的引數

在定義python函式時可定義形參(形式引數的意思),這些形參的值要等到呼叫時才能確定下來,由函式的呼叫者負責為形參傳入值。

關鍵字引數

python函式的引數名不是無意義的,python允許在呼叫函式時通過名字來傳入引數。
按照形參位置傳入的引數被稱為位置引數。如果使用位置引數的方式來傳入引數值,則必須嚴格按照定義函式時指定的順序來傳入引數值;如果根據引數名來傳入引數值,則無需遵守形參定義的順序。這種方式被稱為關鍵字(keyword)引數。

# 定義一個函式
def girth(width , height): 
	print("width: ", width)
	print("height: ", height)
	return 2 * (width + height)
# 傳統呼叫函式的方式,根據位置傳入引數
print(girth(3.5, 4.8))
# 根據關鍵字引數來傳入引數
print(girth(width = 3.5, height = 4.8))
# 使用關鍵字引數時可交換位置
print(girth(height = 4.8, width = 3.5))
# 部分使用關鍵字引數,部分使用位置引數
print(girth(3.5, height = 4.8))

輸出結果:

width:  3.5
height:  4.8
16.6
width:  3.5
height:  4.8
16.6
width:  3.5
height:  4.8
16.6
width:  3.5
height:  4.8
16.6

需要說明的是,如果希望在呼叫函式時混合使用關鍵字引數和位置引數,則關鍵字引數必須位於位置引數之後。換句話說,關鍵字引數之後的只能是關鍵字引數。

# 定義一個函式
def girth(width , height): 
	print("width: ", width)
	print("height: ", height)
	return 2 * (width + height)

# 位置引數必須放在關鍵字引數之前,下面程式碼錯誤
print(girth(width = 3.5, 4.8))

輸出結果:


  File "C:\Users\zz\.spyder-py3\temp.py", line 8
    print(girth(width = 3.5, 4.8))
                            ^
SyntaxError: positional argument follows keyword argument

引數預設值

在某些情況下,程式需要在定義函式時為一個或多個形參指定預設值 --- 這樣在呼叫函式時就可以省略為該形參傳入引數值,而是直接使用該形參的預設值。為形參指定預設值的語法格式如下:

形參名 = 預設值

形參的預設值緊跟在形參之後,中間以英文“=”隔開。

# 為兩個引數指定預設值
def say_hi(name = "孫悟空", message = "歡迎來到瘋狂軟體"):
	print(name, ", 您好")
	print("訊息是:", message)
# 全部使用預設引數
say_hi()
# 只有message引數使用預設值
say_hi("白骨精")
# 兩個引數都不使用預設值
say_hi("白骨精", "歡迎學習Python")
# 只有name引數使用預設值
say_hi(message = "歡迎學習Python")

輸出結果:

孫悟空 , 您好
訊息是: 歡迎來到瘋狂軟體
白骨精 , 您好
訊息是: 歡迎來到瘋狂軟體
白骨精 , 您好
訊息是: 歡迎學習Python
孫悟空 , 您好
訊息是: 歡迎學習Python

如果只傳入一個位置引數,由於該引數位於第一位,系統會將該引數傳給name引數。
由於python要求在呼叫函式時關鍵字引數必須位於位置引數的後面,因此在定義函式時指定了預設值的引數(關鍵字引數)必須在沒有預設值的引數之後。示例:

# 定義一個列印三角形的函式,有預設值的引數必須放在後面
def printTriangle(char, height = 5) :
	for i in range(1, height + 1) :
		# 先列印一排空格
		for j in range(height - i) :
			print(' ', end = '')
		# 再列印一排特殊字元
		for j in range(2 * i - 1) :
			print(char, end = '')
		print()
printTriangle('@', 6)
printTriangle('#', height=7)
printTriangle(char = '*')

輸出結果:

     @
    @@@
   @@@@@
  @@@@@@@
 @@@@@@@@@
@@@@@@@@@@@
      #
     ###
    #####
   #######
  #########
 ###########
#############
    *
   ***
  *****
 *******
*********

引數收集(個數可變的引數)

python允許在形參前面新增一個星號(*),這樣就意味著該引數可接收多個引數值,多個引數值被當成元組傳入。示例:

# 定義了支援引數收集的函式
def test(a, *books) :
    print(books)
    # books被當成元組處理
    for b in books :
        print(b)
    # 輸出整數變數a的值
    print(a)
# 呼叫test()函式
test(5 , "aaa" , "bbb")

輸出結果:

('aaa', 'bbb')
aaa
bbb
5

python允許個數可變的形參可以位於形參列表的任意位置(不要求是形參列表的最後一個引數),但 python要求一個函式最多隻能帶一個支援“普通”引數收集的形參。示例:

# 定義了支援引數收集的函式
def test(*books ,num) :
    print(books)
    # books被當成元組處理
    for b in books :
        print(b)
    print(num)
# 呼叫test()函式
test("瘋狂iOS講義", "瘋狂Android講義", num = 20)

my_list = ["瘋狂Swift講義", "瘋狂Python講義"]
# 將列表的多個元素傳給支援引數收集的引數
test(my_list, num = 20)
my_tuple= ("瘋狂Swift講義", "瘋狂Python講義")
# 將元組的多個元素傳給支援引數收集的引數
test(*my_tuple, num = 20)

輸出結果:

('瘋狂iOS講義', '瘋狂Android講義')
瘋狂iOS講義
瘋狂Android講義
20
(['瘋狂Swift講義', '瘋狂Python講義'],)
['瘋狂Swift講義', '瘋狂Python講義']
20
('瘋狂Swift講義', '瘋狂Python講義')
瘋狂Swift講義
瘋狂Python講義
20

python還可以收集關鍵字引數,此時Python會將這種關鍵字引數收整合字典。為了讓python能收集關鍵字引數,需要在引數前面新增兩個星號。在這種情況下,一個函式可同時包含一個支援“普通”引數收集的引數和一個支援關鍵字引數收集的引數。示例:

# 定義了支援引數收集的函式
def test(x, y, z=3, *books, **scores) :
    print(x, y, z)
    print(books)
    print(scores)
test(1, 2, 3, "瘋狂iOS講義" , "瘋狂Android講義", 語文=89, 數學=94)
test(1, 2, "瘋狂iOS講義" , "瘋狂Android講義", 語文=89, 數學=94)
test(1, 2, 語文=89, 數學=94)

輸出結果:
1 2 3
('瘋狂iOS講義', '瘋狂Android講義')
{'語文': 89, '數學': 94}
1 2 瘋狂iOS講義
('瘋狂Android講義',)
{'語文': 89, '數學': 94}
1 2 3
()
{'語文': 89, '數學': 94}

逆向引數收集

逆向引數收集,指的是在程式已有列表、元組、字典等物件的前提下,把它們的元素“拆開”後傳給函式的引數。
逆向引數收集需要在傳入的列表、元組引數之前新增一個星號,在字典引數之前新增兩個星號。示例1:

def test(name, message):
    print("使用者是: ", name)
    print("歡迎訊息: ", message)
my_list = ['孫悟空', '歡迎來瘋狂軟體']
test(*my_list)

輸出結果:
使用者是:  孫悟空
歡迎訊息:  歡迎來瘋狂軟體

示例2:

def foo(name, *nums):
    print("name引數: ", name)
    print("nums引數: ", nums)
my_tuple = (1, 2, 3)
# 使用逆向收集,將my_tuple元組的元素傳給nums引數
foo('fkit', *my_tuple)

輸出結果:
name引數:  fkit
nums引數:  (1, 2, 3)

示例3:

def foo(name, *nums):
    print("name引數: ", name)
    print("nums引數: ", nums)
my_tuple = (1, 2, 3)
# 使用逆向收集,將my_tuple元組的第一個元素傳給name引數,剩下引數傳給nums引數
foo(*my_tuple)

輸出結果:
name引數:  1
nums引數:  (2, 3)

示例4:

def foo(name, *nums):
    print("name引數: ", name)
    print("nums引數: ", nums)
my_tuple = (1, 2, 3)
# 不使用逆向收集,my_tuple元組整體傳給name引數
foo(my_tuple)

輸出結果:
name引數:  (1, 2, 3)
nums引數:  ()

字典也支援逆向收集,字典將會以關鍵字引數的形式傳入,示例:

def bar(book, price, desc):
    print(book, " 這本書的價格是: ", price)
    print('描述資訊', desc)
my_dict = {'price': 89, 'book': '瘋狂Python講義', 'desc': '這是一本系統全面的Python學習圖書'}
# 按逆向收集的方式將my_dict的多個key-value傳給bar()函式
bar(**my_dict)

輸出結果:
瘋狂Python講義  這本書的價格是:  89
描述資訊 這是一本系統全面的Python學習圖書

函式的引數傳遞機制

python中函式的引數傳遞機制都是“值傳遞”。所謂值傳遞,就是將實際引數值的副本(複製品)傳入函式,而引數本身不會受到任何影響。

def swap(a , b) :
    # 下面程式碼實現a、b變數的值交換
    a, b = b, a
    print("swap函式裡,a的值是", \
        a, ";b的值是", b)
a = 6
b = 9
swap(a , b)
print("交換結束後,變數a的值是", \
    a , ";變數b的值是", b)

    
輸出結果:
swap函式裡,a的值是 9 ;b的值是 6
交換結束後,變數a的值是 6 ;變數b的值是 9

值傳遞的實質:當系統開始執行函式時,系統對形參進行初始化,就是把實參變數的值賦給函式的形參變數,在函式中操作的並不是實際的實參變數。

def swap(dw):
    # 下面程式碼實現dw的a、b兩個元素的值交換
    dw['a'], dw['b'] = dw['b'], dw['a']
    print("swap函式裡,a元素的值是",\
        dw['a'], ";b元素的值是", dw['b'])
    # 把dw直接賦值為None,讓它不再指向任何物件
    dw = None
dw = {'a': 6, 'b': 9}
swap(dw)
print("交換結束後,a元素的值是",\
    dw['a'], ";b元素的值是", dw['b'])
    
輸出結果:
swap函式裡,a元素的值是 9 ;b元素的值是 6
交換結束後,a元素的值是 9 ;b元素的值是 6

變數作用域

在程式中定義一個變數時,這個變數是有作用範圍的,變數的作用範圍被稱為它的作用域。根據定義變數的位置,變數分為兩種:

  • 區域性變數。在函式中定義的變數,包括引數,都被稱為區域性變數
  • 全域性變數。在函式外面、全域性範圍內定義的變數,被稱為全域性變數

每個函式在執行時,系統都會為該函式分配一塊“臨時記憶體空間”,所有的區域性變數都被儲存在這塊臨時記憶體空間內。當函式執行完成後,這塊記憶體空間就被釋放了,這些區域性變數也就失效了。因此離開函式之後就不能再訪問區域性變量了。
全域性變數意味著它們可以在所有函式內被訪問。
不管是在函式的區域性範圍內還是全域性範圍內,都可能存在多個變數,每個變數“持有”該變數的值。從這個角度看,不管是區域性變數還是全域性變數,這些變數和它們的值就像一個“看不見”的字典,其中變數名就是字典的Key,變數值就是字典的value。
python提供了三個函式來獲取指定範圍內的“變數字典”

  • globals():該函式返回全域性範圍內所有變數組成的“變數字典”
  • locals():該函式返回當前區域性範圍內所有變數組成的“變數字典”
  • vars(object):獲取在指定物件範圍內所有變數組成的“變數字典”。如果不傳入object引數,vars()和locals()的作用完全相同。

globals()和locals()這兩個函式的區別和聯絡:

  • locals()總是獲取當前區域性範圍內所有變數組成的“變數字典”,因此,如果在全域性範圍內(在函式之外)呼叫locals()函式,同樣會獲取全域性範圍內所有變數組成的“變數字典”;而globals()無論在哪裡執行,總是獲取全域性範圍內所有變數組成的“變數字典”
  • 一般來說,使用globals()和locals()獲取的“變數字典”只應該被訪問,不應該被修改。但實際上,不管是使用globals()還是使用locals()獲取的全域性範圍內的“變數字典”,都可以被修改。而這種修改會真正改變全域性變數本身;但通過locals()獲取的區域性範圍內的“變數字典”,即使對它修改也不會影響區域性變數。
def test ():
    age = 20
    # 直接訪問age區域性變數
    print(age) # 輸出20
    # 訪問函式區域性範圍的“變數陣列”
    print(locals()) # {'age': 20}
    # 通過函式區域性範圍的“變數陣列”訪問age變數
    print(locals()['age']) # 20
    # 通過locals函式區域性範圍的“變數陣列”改變age變數的值
    locals()['age'] = 12
    # 再次訪問age變數的值
    print('xxx', age) # 依然輸出20
    # 通過globals函式修改x全域性變數
    globals()['x'] = 19
x = 5
y = 20
print(globals()) # {..., 'x': 5, 'y': 20}
# 在全域性訪問內使用locals函式,訪問的是全域性變數的“變數陣列”
print(locals()) # {..., 'x': 5, 'y': 20}
# 直接訪問x全域性變數
print(x) # 5
# 通過全域性變數的“變數陣列”訪問x全域性變數
print(globals()['x']) # 5
# 通過全域性變數的“變數陣列”對x全域性變數賦值
globals()['x'] = 39
print(x) # 輸出39
# 在全域性範圍內使用locals函式對x全域性變數賦值
locals()['x'] = 99
print(x) # 輸出99

提示:
在使用globals()或locals()訪問全域性變數的“變數字典”時,將會看到程式輸出的“變數字典”預設包含了很多變數,這些都是Python主程式內建的。
全域性變數預設可以在所有函式內被訪問,但如果在函式中定義了與全域性變數同名的變數,此時就會發生區域性變數遮蔽(hide)全域性變數的情形。示例:

name = 'Charlie'
def test ():
    # 直接訪問name全域性變數
    print(name) # Charlie
test()
print(name)

輸出結果:
Charlie
Charlie

函式內定義了與全域性變數同名的變數,發生區域性變數遮蔽(hide)全域性變數的情形,示例:

name = 'Charlie'
def test ():
    # 直接訪問name全域性變數
    print(name) # Charlie
    name= "孫悟空"
test()
print(name)


輸出結果:
Traceback (most recent call last):

  File "C:\Users\zz\.spyder-py3\temp.py", line 6, in <module>
    test()

  File "C:\Users\zz\.spyder-py3\temp.py", line 4, in test
    print(name) # Charlie

UnboundLocalError: local variable 'name' referenced before assignment

為了避免這個問題,可以通過兩種方式來修改上面程式

  1. 訪問被遮蔽的全域性變數

可以通過globals()函式來實現,修改如下:

name = 'Charlie'
def test ():
    # 直接訪問name全域性變數
    print(globals()['name'])  # Charlie
    name = '孫悟空'
test()
print(name)  # Charlie

輸出結果:
Charlie
Charlie
  1. 在函式中宣告全域性變數

為了避免在函式中對全域性變數賦值(並不是重新定義區域性變數),可使用global語句來宣告全域性變數。可將程式修改如下:

name = 'Charlie'
def test ():
    # 宣告name是全域性變數,後面的賦值語句不會重新定義區域性變數
    global name
    # 直接訪問name全域性變數
    print(name)  # Charlie
    name = '孫悟空'
print(name)
test()
print(name)  # 孫悟空

輸出結果:
Charlie
Charlie
孫悟空

增加了“global name”宣告語句之後,程式會把name變數當成全域性變數,這意味著test()函式後面對name賦值的語句只是對全域性變數賦值,而不是重新定義區域性變數。

區域性函式

前面所看到的函式都是在全域性範圍內定義的,它們都是全域性函式。python還支援在函式體內定義函式,這種被放在函式體內定義的函式稱為區域性函式。
在預設情況下,區域性函式對外部是隱藏的,區域性函式只能在其封閉(enclosing)函式內有效,其封閉函式也可以返回函式,以便程式在其他作用域中使用區域性函式。

# 定義函式,該函式會包含區域性函式
def get_math_func(type, nn) :
    # 定義一個計算平方的區域性函式
    def square(n) :  # ①
        return n * n
    # 定義一個計算立方的區域性函式
    def cube(n) :  # ②
        return n * n * n
    # 定義一個計算階乘的區域性函式
    def factorial(n) :   # ③
        result = 1
        for index in range(2, n + 1) :
            result *= index
        return result
    # 呼叫區域性函式
    if type == "square" :
        return square(nn)
    elif type == "cube":
        return cube(nn)
    else:
        return factorial(nn)
print(get_math_func("square", 3)) # 輸出9
print(get_math_func("cube", 3)) # 輸出27
print(get_math_func("", 3)) # 輸出6

輸出結果:
9
27
6

如果封閉函式沒有返回區域性函式,那麼區域性函式只能在封閉函式內呼叫,如上面程式。
如果封閉函式將區域性函式返回,且程式使用變數儲存了封閉函式的返回值,那麼這些區域性函式的作用域就會被擴大。因此程式完全可以自由地呼叫他們,就像它們都是全域性函式一樣。
區域性函式內的變數也會遮蔽它所在函式內的區域性變數,示例:

def foo ():
    # 區域性變數name
    name = 'Charlie'
    def bar ():
        # 訪問bar函式所在的foo函式的name區域性變數
        print(name) # Charlie
        name = '孫悟空'
    bar()
foo()

輸出結果:
Traceback (most recent call last):

  File "C:\Users\zz\.spyder-py3\temp.py", line 9, in <module>
    foo()

  File "C:\Users\zz\.spyder-py3\temp.py", line 8, in foo
    bar()

  File "C:\Users\zz\.spyder-py3\temp.py", line 6, in bar
    print(name) # Charlie

UnboundLocalError: local variable 'name' referenced before assignment

python提供了nonlocal關鍵字,通過nonlocal語句即可宣告區域性函式的區域性變數只是訪問該函式所在函式內的佈局變數(並沒有定義新的佈局變數)。示例:

def foo ():
    # 區域性變數name
    name = 'Charlie'
    def bar ():
        nonlocal name
        # 訪問bar函式所在的foo函式的name區域性變數
        print(name) # Charlie
        name = '孫悟空'
    bar()
    print(name)
foo()

輸出結果:
Charlie
孫悟空

提示
nonlocal與前面介紹的global功能大致相似,區別只是global用於宣告訪問全域性變數,而nonlocal用於宣告訪問當前函式所在函式內的佈局變數。

函式的高階內容

python的函式本身也是一個物件,函式即可用於賦值,也可用作其他函式的引數,還可作為其他函式的返回值。

使用函式變數

python的函式也是一種值:所有函式都是function物件,這意味著可以把函式本身賦值給變數,就像把整數、浮點數、列表、元組賦值給變數一樣。
當把函式賦值給變數之後,可通過該變數來呼叫函式,示例:

# 定義一個計算乘方的函式
def pow(base, exponent) :
	result = 1
	for i in range(1, exponent + 1) :
		result *= base
	return result
# 將pow函式賦值給my_fun,則my_fun可當成pow使用
my_fun = pow
print(my_fun(3 , 4)) # 輸出81
# 定義一個計算面積的函式
def area(width, height) :
	return width * height
# 將area函式賦值給my_fun,則my_fun可當成area使用
my_fun = area
print(my_fun(3, 4)) # 輸出12

輸出結果:
81
12

通過對my_fun變數賦值為不同的函式,可以讓my_fun變數在不同的時間指向不同的函式,從而讓程式更加靈活。由此可見,使用函式變數的好處是讓程式更加靈活。

使用函式作為函式形參

有時候需要定義一個函式,該函式的大部分計算邏輯都能確定,但某些處理邏輯暫時無法確定 --- 這意味著某些程式程式碼需要動態改變,如果希望呼叫函式時能動態傳入這些程式碼,那麼就需要在函式中定義函式形參,這樣既可在呼叫該函式時傳入不同的函式作為引數,從而動態改變這段程式碼。

# 定義函式型別的形參,其中fn是一個函式
def map(data, fn) :    
    result = []
    # 遍歷data列表中每個元素,並用fn函式對每個元素進行計算
    # 然後將計算結果作為新陣列的元素
    for e in data :
        result.append(fn(e))
    return result
# 定義一個計算平方的函式
def square(n) :
    return n * n
# 定義一個計算立方的函式
def cube(n) :
    return n * n * n
# 定義一個計算階乘的函式
def factorial(n) :
    result = 1
    for index in range(2, n + 1) :
        result *= index
    return result
data = [3 , 4 , 9 , 5, 8]
print("原資料: ", data)
# 下面程式程式碼3次呼叫map()函式,每次呼叫時傳入不同的函式
print("計算陣列元素的平方")
print(map(data , square))
print("計算陣列元素的立方")
print(map(data , cube))
print("計算陣列元素的階乘")
print(map(data , factorial))
# 獲取map的型別
print(type(map))


輸出結果:
原資料:  [3, 4, 9, 5, 8]
計算陣列元素的平方
[9, 16, 81, 25, 64]
計算陣列元素的立方
[27, 64, 729, 125, 512]
計算陣列元素的階乘
[6, 24, 362880, 120, 40320]
<class 'function'>

從上面介紹不難看出,通過使用函式作為引數可以在呼叫函式時動態傳入函式 --- 實際上就可以動態改變被呼叫函式的部分程式碼。

使用函式作為返回值

python還支援使用函式作為其他函式的返回值。示例:

def get_math_func(type) :
    # 定義一個計算平方的區域性函式
    def square(n) :  # ①
        return n * n
    # 定義一個計算立方的區域性函式
    def cube(n) :  # ②
        return n * n * n
    # 定義一個計算階乘的區域性函式
    def factorial(n) :   # ③
        result = 1
        for index in range(2 , n + 1): 
            result *= index
        return result
    # 返回區域性函式
    if type == "square" :
        return square
    if type == "cube" :
        return cube
    else:
        return factorial
# 呼叫get_math_func(),程式返回一個巢狀函式
math_func = get_math_func("cube") # 得到cube函式
print(math_func(5)) # 輸出125
math_func = get_math_func("square") # 得到square函式
print(math_func(5)) # 輸出25
math_func = get_math_func("other") # 得到factorial函式
print(math_func(5)) # 輸出120


輸出結果:
125
25
120

區域性函式與lambda表示式

lambda表示式時現代程式語言爭相引入的一種語法,如果說函式是命名的、方便複用程式碼塊,那麼lambda表示式則是功能更靈活的程式碼塊,它可以在程式中被傳遞和呼叫。

回顧區域性函式

回顧前面介紹的 get_math_func 函式將返回三個區域性函式之一,如下程式碼:

def get_math_func(type) :
    # 定義一個計算平方的區域性函式
    def square(n) :  # ①
        return n * n
    # 定義一個計算立方的區域性函式
    def cube(n) :  # ②
        return n * n * n
    # 定義一個計算階乘的區域性函式
    def factorial(n) :   # ③
        result = 1
        for index in range(2 , n + 1): 
            result *= index
        return result
    # 返回區域性函式
    if type == "square" :
        return square
    if type == "cube" :
        return cube
    else:
        return factorial

由於區域性函式的作用域預設僅停留在其封閉函式之內,因此這三個區域性函式的函式名的作用太有限了 --- 僅僅是在if語句中作為返回值使用。一旦離開了get_math_func函式體,這三個區域性函式的函式名就失去了意義。
既然區域性函式的函式名沒有太大意義,那麼就考慮使用lambda表示式來簡化區域性函式的寫法。

使用lambda表示式代替區域性函式

使用lambda表示式來簡化get_math_func函式,將程式改寫如下:

def get_math_func(type) :
    result=1
    # 該函式返回的是Lambda表示式
    if type == 'square':
        return lambda n: n * n  # ①
    elif type == 'cube':
        return lambda n: n * n * n  # ②
    else:
        return lambda n: (1 + n) * n / 2 # ③
# 呼叫get_math_func(),程式返回一個巢狀函式
math_func = get_math_func("cube")
print(math_func(5)) # 輸出125
math_func = get_math_func("square")
print(math_func(5)) # 輸出25
math_func = get_math_func("other")
print(math_func(5)) # 輸出15.0

輸出結果:
125
25
15.0

python要求lambda表示式只能是單行表示式,不允許使用更復雜的函式形式。
lambda表示式的語法格式如下:

lambda [parameter_list]: 表示式

lambda表示式的幾個要點:

  • lambda表示式必須使用lambda關鍵字定義
  • 在lambda關鍵字之後、冒號左邊的是引數列表,可以沒有引數,也可以有多個引數。如果有多個引數,則需要用逗號隔開,冒號右邊是該lambda表示式的返回值。

實際上,lambda表示式的本質是匿名的、單行函式體的函式。因此,lambda表示式可以寫成函式的形式。例如,對於如下lambda表示式:

lambda x,y:x+y

可改寫如下函式形式:

def add(x,y):return x+y

上面定義函式時使用了簡化語法:當函式體只有一行程式碼時,可以直接把函式體的程式碼放在與函式頭同一行。
總體來說,函式比lambda表示式的適應性更強,lambda表示式只能建立簡單的物件(它只適合函式體為單行的情形。但lambda表示式依然有如下用途:

  • 對於單行函式,使用lambda表示式可以省去函式定義的過程,讓程式碼更加簡潔。
  • 對於不需要多次複用的函式,使用lambda表示式可以在用完之後立即釋放,提供了效能。
# 傳入計算平方的lambda表示式作為引數
x = map(lambda x: x*x , range(8))
print([e for e in x]) # [0, 1, 4, 9, 16, 25, 36, 49]
# 傳入計算平方的lambda表示式作為引數
y = map(lambda x: x*x if x % 2 == 0 else 0, range(8))
print([e for e in y]) # [0, 0, 4, 0, 16, 0, 36, 0]

輸出結果:
[0, 1, 4, 9, 16, 25, 36, 49]
[0, 0, 4, 0, 16, 0, 36, 0]

python內建的map()函式的第一個引數需要傳入函式,此處傳入了函式的簡化形式:lambda表示式,這樣程式更加簡潔,而且效能更好。

本章小結

python語言既支援面向過程程式設計,也支援面向物件程式設計。而函式和lambda表示式就是Python面向過程程式設計的語法基礎。必須引起重視。


原文來源於我的語雀,我的微信公眾號:細細研磨