Python函數和函數式編程

第5章 函數和函數式編程

5.1 引言
函數是組織好的,可重復使用的,用來實現單一,或相關聯功能的代碼段。函數能提高應用的模塊性,和代碼的重復利用率。在Python中有很多內建函數,比如print()。當然隨著學習的深入,你也可以學會創建對自己有用的函數(用戶自定義函數)。簡單的理解下函數的概念,就是用戶編寫了一些語句,為了方便使用這些語句,把這些語句組合在一起,給它起一個名字。使用的時候只要調用這個名字,就可以實現語句組的功能了。
在沒用過函數之前,我們要計算一個數的冪時會用到**,方法是這樣的:

>>>2**3
8 #此處為[**python 函數返回值**](http://www.iplaypy.com/jinjie/return.html "python 返回值")

現在知道了函數,就可以用內建函數pow來計算乘方了:

>>>pow(2,3)
8

5.2 調用函數
python系統中自帶的一些函數就叫做內建函數,比如:dir()type()等等,不需要我們自己編寫。還有一種是第三方函數,就是其它程序員編好的一些函數,共享給大家使用。這兩種函數都是拿來就可以直接使用的。最后就是我們自己編些的方便自己工作學習用的函數,就叫做自定義函數了。
Python內置了很多有用的函數,我們可以直接調用。要調用一個函數,需要知道函數的名稱和參數,比如求絕對值的函數abs,只有一個參數。可以直接從Python的官方網站查看文檔:
http://docs.python.org/3/library/functions.html#abs
也可以在交互式命令行通過help(abs)查看abs函數的幫助信息。
調用abs函數:

>>> abs(100)
100
>>> abs(-10)
10

比如max函數可以接收任意多個參數,并返回最大的那個:

>>> max(1, 2)
2
>>> max(-2, 0, 1, 4)
4

同時在調用函數的時候,需要注意傳入的參數數量是否符合要求。例如當調用abs函數時,若傳入的參數數量不對,會報出TypeError的錯誤,并且Python會明確地告訴你:abs()有且僅有1個參數,但給出了兩個:

>>> abs(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: abs() takes exactly one argument (2 given)

如果傳入的參數數量是對的,但參數類型不能被函數所接受,也會報TypeError的錯誤,并且給出錯誤信息:str是錯誤的參數類型:

>>> abs('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'

數據類型轉換
Python內置的常用函數還包括數據類型轉換函數,比如int()函數可以把其他數據類型轉換為整數:

>>> int('12')
12
>>> int(12.3)
12
>>> float('12.3')
12.3
>>> str(1.23)
'1.23'
>>> str(10)
'10'
>>> bool(1)
True
>>> bool('')
False

也可以把函數名賦給一個變量,相當于給這個函數起了一個“別名”:

>>> a = abs # 變量a指向abs函數
>>> a(-1) # 通過a調用abs函數
1

定義一個函數:給了函數一個名稱,指定了函數里包含的參數,和代碼塊結構。
這個函數的基本結構完成以后,用戶可以通過另一個函數調用執行,也可以直接從 Python 命令提示符執行。
如下實例調用了 printme() 函數:

#!/usr/bin/python3
 
# 定義函數
def printme( str ):
   "打印傳入的字符串"
   print (str);
   return;
 
# 調用函數
printme("調用用戶自定義函數!");
printme("再次調用同一函數");
調用用戶自定義函數!
再次調用同一函數

小結
調用Python的函數,需要根據函數定義,傳入正確的參數。
如果函數調用出錯,一定要注意看錯誤信息!

5.3 定義函數
在Python中,定義一個具有特定功能的函數需要符合一定規則:

  1. 函數代碼塊以 def 關鍵詞開頭,后接函數標識符名稱和圓括號 ()。
  2. 任何傳入參數和自變量必須放在圓括號中間,圓括號之間可以用于定義參數。
  3. 函數的第一行語句可以選擇性地使用文檔字符串—用于存放函數說明。
    函數內容以冒號起始,并且縮進。
  4. return [表達式] 結束函數,選擇性地返回一個值給調用方。不帶表達式的return相當于返回 None。
    一般格式如下:
def 函數名(參數列表):    
    函數體

默認情況下,參數值和參數名稱是按函數聲明中定義的的順序匹配起來的。
我們以自定義一個輸出"Hello World!"的函數為例:

>>> def hello() :
   print("Hello World!")
 
>>> hello()
Hello World!

在更復雜的應用中,函數中帶上參數變量:

#!/usr/bin/python3

# 計算面積函數
def area(width, height):
    return width * height
 
def print_welcome(name):
    print("Welcome", name)

print_welcome("Runoob")
w = 4
h = 5
print("width =", w, " height =", h, " area =", area(w, h))
Welcome Runoob
width = 4  height = 5  area = 20

注意:函數體內部語句執行時,一旦執行到return時,函數就執行完畢,并將結果返回。因此,函數內部通過條件判斷和循環可以實現非常復雜的邏輯。
注意:如果沒有return語句,函數執行完畢后也會返回結果,只是結果為None。(return None可以簡寫為return)

如果用戶已經把hello()的函數定義保存為test.py文件了,那么,可以在該文件的當前目錄下啟動Python解釋器,用from test import hello來導入hello()函數,注意test是文件名(不含.py擴展名):

>>> from test import hello                     
>>> hello()
Hello World!

import的用法在后續“模塊”一節中會詳細介紹。

空函數
如果想定義一個什么事也不做的空函數,可以用pass語句:

def nop():
    pass

pass可以用來作為占位符,比如現在還沒想好怎么寫函數的代碼,就可以先放一個pass,讓代碼能運行起來。

參數傳遞
在 python 中,類型屬于對象,變量是沒有類型的:

a=[1,2,3]

a="Runoob"

以上代碼中,[1,2,3] 是 List 類型,"Runoob" 是 String 類型,而變量 a 是沒有類型,她僅僅是一個對象的引用(一個指針),可以是指向 List 類型對象,也可以是指向 String 類型對象。

可更改(mutable)與不可更改(immutable)對象
在 python 中,strings, tuples, 和 numbers 是不可更改的對象,而 list,dict 等則是可以修改的對象。

  1. 不可變類型:變量賦值 a=5 后再賦值 a=10,這里實際是新生成一個 int 值對象 10,再讓 a 指向它,而 5 被丟棄,不是改變a的值,相當于新生成了a。
  2. 可變類型:變量賦值 la=[1,2,3,4] 后再賦值 la[2]=5 則是將 list la 的第三個元素值更改,本身la沒有動,只是其內部的一部分值被修改了。

python 函數的參數傳遞:

  1. 不可變類型:類似 c++ 的值傳遞,如 整數、字符串、元組。如fun(a),傳遞的只是a的值,沒有影響a對象本身。比如在 fun(a)內部修改 a 的值,只是修改另一個復制的對象,不會影響 a 本身。
  2. 可變類型:類似 c++ 的引用傳遞,如 列表,字典。如 fun(la),則是將 la 真正的傳過去,修改后fun外部的la也會受影響。
    python 中一切都是對象,嚴格意義我們不能說值傳遞還是引用傳遞,我們應該說傳不可變對象和傳可變對象。
    python 傳不可變對象實例
#!/usr/bin/python3
 
def ChangeInt( a ):
    a = 10
b = 2
ChangeInt(b)
print( b ) # 結果是 2

實例中有 int 對象 2,指向它的變量是 b,在傳遞給 ChangeInt 函數時,按傳值的方式復制了變量 b,a 和 b 都指向了同一個 Int 對象,在 a=10 時,則新生成一個 int 值對象 10,并讓 a 指向它。
傳可變對象實例
可變對象在函數里修改了參數,那么在調用這個函數的函數里,原始的參數也被改變了。例如:

#!/usr/bin/python3
 
# 可寫函數說明
def changeme( mylist ):
   "修改傳入的列表"
   mylist.append([1,2,3,4]);
   print ("函數內取值: ", mylist)
   return
 
# 調用changeme函數
mylist = [10,20,30];
changeme( mylist );
print ("函數外取值: ", mylist)

傳入函數的和在末尾添加新內容的對象用的是同一個引用。故輸出結果如下:

函數內取值:  [10, 20, 30, [1, 2, 3, 4]]
函數外取值:  [10, 20, 30, [1, 2, 3, 4]]

小結
定義函數時,需要確定函數名和參數個數;
如果有必要,可以先對參數的數據類型做檢查;
函數體內部可以用return隨時返回函數結果;
函數執行完畢也沒有return語句時,自動return None。

5.4 函數的參數
在定義函數時,我們把參數的名字和位置確定后,函數的接口定義就完成了。對于函數的調用者來說,只需知道如何傳遞正確的參數,以及函數的返回值即可,函數內部被封裝起來,調用者無需了解。
Python的函數定義非常簡單,但靈活度卻非常大。除了正常定義的必選參數外,還可以使用默認參數、可變參數和關鍵字參數,使得函數定義出來的接口,不但能處理復雜的參數,還可以簡化調用者的代碼。

鑒于函數定義中可能包含多個形參,因此函數調用中也可能包含多個實參。想函數傳遞實參的方式很多,可使用位置實參,和要求實參的順序相同;也可使用關鍵字實參,其中每個實參都由變量名和值組成,等等。以下是調用函數時可使用的正式參數類型:
位置參數

5.4.1位置實參
你調用函數時,Python必須將函數調用中的每個實參都關聯到函數定義的一個形參。為此,最簡單的關聯方式是基于實參的順序,這種關聯方式被稱為位置實參,可以多次調用。
為明白其中的工作原理,來看一個顯示學生信息的函數。這個函數指出一名學生的名字以及年齡,如下所示:

? def describe_student(person_name, student_age):
      """顯示學生的信息"""
      print("\nMy name is " + person_name + ".")     
      print(person_name + " is " + student_age+ " years old.")

? describe_student('Jack', '18')

這個函數的定義表明,它需要一名學生的姓名和一個年齡數字(見?)。調用describe_student() 時,需要按順序提供一名學生的姓名和一個年齡數字。例如,在前面的函數調用中,實參'Jack' 存儲在形參person_name中,而實參'18' 存儲在形參student_age 中(見?)。在函數體內,使用了這兩個形參來顯示學生的信息。輸出描述了一名18歲的學生Jack:

My name is Jack.
Jack is 18 years old.
  1. 調用函數多次
    用戶可以根據需要調用函數任意次。要再描述一名學生,只需再次調用describe_student() 即可:
def describe_student(person_name, student_age):
    """顯示學生的信息"""
    print("\nMy name is " + person_name + ".")     
    print(person_name + " is " + student_age+ " years old.")

describe_student('Jack', '18')
describe_student('Bob', '17')

第二次調用describe_student() 函數時,我們向它傳遞了實參'Bob' 和'17' 。與第一次調用時一樣,Python將實參'Bob' 關聯到形參person_name,并將實參'17' 關聯到形參student_age 。與前面一樣,這個函數完成其任務,但打印的是一名17歲的學生Bob的信息。至此,我們描述了一名18歲的學生Jack和17歲的學生Bob.

My name is Jack.
Jack is 18 years old.
My name is Bob.
Jack is 17 years old.

調用函數多次是一種效率極高的工作方式。我們只需在函數中編寫描述學生的代碼一次,然后每當需要描述新學生時,都可調用這個函數,并向它提供新學生的信息。即便描述全校的學生,用戶依然只需使用一行調用函數的代碼,就可描述一名新學生。
在函數中,可根據需要使用任意數量的位置實參,Python將按順序將函數調用中的實參關聯到函數定義中相應的形參。

  1. 位置實參的順序很重要
    使用位置實參來調用函數時,如果實參的順序不正確,結果可能出乎意料:
def describe_student(person_name, student_age):
    """顯示學生的信息"""
    print("\nMy name is " + person_name + ".")     
    print(person_name + " is " + student_age+ " years old.")

describe_student('18', 'Jack')

在這個函數調用中,我們先指定名字,再指定學生年齡。由于實參'18' 在前,這個值將存儲到形參person_name中;同理,'Jack' 將存儲到形參student_age中。結果是我們得到了一名年齡為Jack的18:

My name is 18.
Jack is Jack years old.

如果結果像上面一樣荒謬,請確認函數調用中實參的順序與函數定義中形參的順序一致。

5.4.2 默認參數
編寫函數時,可給每個形參指定默認值 。在調用函數中給形參提供了實參時,Python將使用指定的實參值;否則,將使用形參的默認值。因此,給形參指定默認值后,可在函數調用中省略相應的實參。使用默認值可簡化函數調用,還可清楚地指出函數的典型用法。
例如describe_student函數定義沒有問題,但若如下調用會出現錯誤:

>>> describe_student('Jack')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: describe_student() missing 1 required positional argument: 'student_age'

Python的錯誤信息很明確:調用函數describe_student()缺少了一個位置參數student_age。這個時候,默認參數就排上用場了。若大部分學生的年齡為18歲,我們可以把第二個參數student_age的默認值設定為18:

def describe_student(person_name, student_age=18):
    """顯示學生的信息"""
    print("\nMy name is " + person_name + ".")     
    print(person_name + " is " + student_age+ " years old.")

這樣,當我們調用describe_student(Jack)時,相當于調用describe_student(Jack,18):

>>> describe_student('Jack')
My name is Jack.
Jack is 18 years old.

>>> describe_student('Jack','18')
My name is Jack.
Jack is 18 years old.

而對于student > 18的其他情況,就必須明確地傳入student_age,比如describe_student(Herbie,19)。
從上面的例子可以看出,默認參數可以簡化函數的調用。
注意:
設置默認參數時,必選參數在前,默認參數在后,否則Python的解釋器會報錯(思考一下為什么默認參數不能放在必選參數前面)

當函數有多個參數時,把變化大的參數放前面,變化小的參數放后面。變化小的參數就可以作為默認參數。
使用默認參數有什么好處?最大的好處是能降低調用函數的難度。
舉個例子,我們編寫一個學生注冊的函數,需要傳入name和gender兩個參數:

def enroll(name, gender):
    """注冊學生的信息"""
    print("name: ",name)     
    print("gender: ",gender)

這樣,調用enroll()函數只需要傳入兩個參數:

>>> enroll('Jack', 'F')
name: Jack
gender: F

如果要繼續傳入年齡、城市等信息怎么辦?這樣會使得調用函數的復雜度大大增加。
我們可以把年齡和城市設為默認參數:

def enroll(name, gender,age=18, city='Beijing'):
    print('name: ', name)
    print('gender: ', gender)
    print('age: ', age)
    print('city:', city)

這樣,大多數學生注冊時不需要填寫年齡和城市,只提供必須的兩個參數:

>>> enroll('Sarah', 'F')
name: Sarah
gender: F
age: 18
city: Beijing

只有與默認參數不符的學生才需要提供額外的信息:

enroll('Bob', 'M', 17)
enroll('Adam', 'M', city='Tianjin')

可見,默認參數降低了函數調用的難度,而一旦需要更復雜的調用時,又可以傳遞更多的參數來實現。無論是簡單調用還是復雜調用,函數只需要定義一個。
有多個默認參數時,調用的時候,既可以按順序提供默認參數,比如調用enroll('Bob', 'M', 17),意思是,除了name,gender這兩個參數外,最后一個參數應用在參數age上,city參數由于沒有提供,仍然使用默認值。也可以不按順序提供部分默認參數。當不按順序提供部分默認參數時,需要把參數名寫上。比如調用enroll('Adam', 'M', city='Tianjin'),意思是,city參數用傳進去的值,其他默認參數繼續使用默認值。
默認參數很有用,但定義默認參數時要牢記一點:默認參數必須指向不變對象!否則會出現重大錯誤,例如:
先定義一個函數,傳入一個list,添加一個END再返回:

def test_add(H=[]):
    H.append('END')
    return H

當正常調用時,結果似乎不錯:

>>> test_add([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['a', 'b', 'c'])
['a', 'b', 'c', 'END']

當初次使用默認參數調用時,結果也是對的:

>>> test_add()
['END']

但是當再次調用test_add()時,結果就出現錯誤了:

>>> test_add()
['END', 'END']
>>> test_add()
['END', 'END', 'END']

我們會發現,默認參數是[],但是函數test_add()似乎每次都“記住了”上次添加了'END'后的list。這是為什么呢?原因如下:
Python函數在定義的時候,默認參數H的值就被計算出來了,即[],因為默認參數H也是一個變量,它指向對象[],每次調用該函數,如果改變了H的內容,則下次調用時,默認參數的內容就變了,不再是函數定義時的[]了。
注意:定義默認參數時,默認參數必須指向不變對象!
我們可以用None這個不變對象來解決此問題:

def test_add(H=None):
    if H is None:
        H = []
    H.append('END')
    return H

現在,無論調用多少次,都不會有問題:

>>> test_add()
['END']
>>> test_add()
['END']

為什么要設計str、None這樣的不變對象呢?因為不變對象一旦創建,對象內部的數據就不能修改,這樣就減少了由于修改數據導致的錯誤。此外,由于對象不變,多任務環境下同時讀取對象不需要加鎖,同時讀一點問題都沒有。我們在編寫程序時,如果可以設計一個不變對象,那就盡量設計成不變對象。

5.4.3 不定長參數
在Python函數中,還可以定義不定長參數,也叫可變參數。顧名思義,不定長參數就是傳入的參數個數是可變的。
我們以數學題為例子,給定一組數字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, 2, 3, 4))
10

但若把函數的參數改為不定長參數:

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n 
    return sum

則調用函數的方式可以簡化成這樣:

>>> calc(1, 2, 3)
6
>>> calc(1, 2, 3, 4)
10

定義不定長參數和定義一個list或tuple參數相比,僅僅在參數前面加了一個*號。在函數內部,參數numbers接收到的是一個tuple,因此,函數代碼完全不變。但是,調用該函數時,可以傳入任意個參數,包括0個參數:

>>> calc()
0

如果已經有一個list或者tuple,要調用一個不定長參數可如下:

>>> nums = [1, 2, 3]
>>> calc(*nums)
6

*nums表示把nums這個list的所有元素作為可變參數傳進去。這種寫法相當有用,而且很常見。

5.4.4 關鍵字參數
關鍵字實參是傳遞給函數的名稱—值對。你直接在實參中將名稱和值關聯起來了,因此向函數傳遞實參時不會混淆(不會得到名為18的Jack這樣的結果)。關鍵字實參讓用戶無需考慮函數調用中的實參順序,還清楚地指出了函數調用中各個值的用途。
下面重新編寫describe_student()函數,在其中使用關鍵字實參來調用describe_student():

def describe_student(person_name, student_age):
    """顯示學生的信息"""
    print("\nMy name is " + person_name + ".")     
    print(person_name + " is " + student_age+ " years old.")

describe_student(person_name='Jack', student_age='18')

函數describe_pet() 還是原來那樣,但調用這個函數時,我們向Python明確地指出了各個實參對應的形參。看到這個函數調用時,Python知道應該將實參'Jack' 和'18' 分別存儲在形參person_name和 student_age中。輸出正確無誤,它指出我們有一名名叫Jack,年齡為18歲的學生。關鍵字實參的順序無關緊要,因為Python知道各個值該存儲到哪個形參中。下面兩個函數調用是等效的:

describe_pet(animal_type='hamster', pet_name='harry') 
describe_pet(pet_name='harry', animal_type='hamster')

注意:使用關鍵字實參時,務必準確地指定函數定義中的形參名。

不定長參數允許傳入0個或任意個參數,這些不定長參數在函數調用時自動組裝為一個元組(tuple)。而關鍵字參數允許你傳入0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝為一個字典(dict)。如下,函數person除了必選參數name和age外,還接受關鍵字參數kw。在調用該函數時,可以只傳入必選參數:

def enroll(name, age,**kw)):
    print('name: ', name, 'age: ', age, 'other: ', kw)

>>> enroll('Michael', 18)
name: Michael age: 18 other: {}

也可以傳入任意個數的關鍵字參數:

>>> enroll('Bob', 17, city='Beijing')
name: Bob age: 17 other: {'city': 'Beijing'}
>>> enroll('Adam', 19, gender='M', job='Engineer')
name: Adam age: 19 other: {'gender': 'M', 'job': 'Engineer'}

關鍵字參數有擴展函數的功能。比如,在enroll函數里,我們保證能接收到name和age這兩個參數,但是,如果調用者愿意提供更多的參數,我們也能收到。試想你正在做一個用戶注冊的功能,除了用戶名和年齡是必填項外,其他都是可選項,利用關鍵字參數來定義這個函數就能滿足注冊的需求。
和不定長參數類似,也可以先組裝出一個dict,然后,把該dict轉換為關鍵字參數傳進去:

>>> extra = {'gender':'M','city': 'Beijing'}
>>> enroll('Jack', 18, gender=extra['M'], city=extra['city'])
name: Jack age: 18 other: {'gender':'M','city': 'Beijing'}

當然,上面復雜的調用可以用簡化的寫法:

>>> extra = {'gender':'M','city': 'Beijing'}
>>> enroll('Jack', 18, **extra)
name: Jack age: 18 other: {'gender':'M','city': 'Beijing'}

注意:**extra表示把extra這個dict的所有key-value用關鍵字參數傳入到函數的**kw參數,kw將獲得一個dict,注意kw獲得的dict是extra的一份拷貝,對kw的改動不會影響到函數外的extra。

5.4.5 命名關鍵字參數
如果要限制關鍵字參數的名字,就可以用命名關鍵字參數。
和關鍵字參數**kw不同,如果沒有可變參數,命名關鍵字參數就必須加一個*作為特殊分隔符。如果缺少*,Python解釋器將無法識別位置參數和命名關鍵字參數。例如,若只接收age和city作為關鍵字參數。這種方式定義的函數如下:

def enroll(name, gender, *, age, city):
    print(name, gender, age, city)

>>> enroll('Jack', ' M', age='18', city='Beijing')
Jack M 18 Beijing

如果函數定義中已經有了一個可變參數,后面跟著的命名關鍵字參數就不再需要一個特殊分隔符*了:

def enroll(name, gender, *grade, age, city):
    print(name, gender, grader, age, city)

注意:和位置參數不同,命名關鍵字參數必須傳入參數名。
如果沒有傳入參數名,調用將報錯:

>>> enroll(' Jack',' M',' 18',' Beijing')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: enroll() takes 2 positional arguments but 4 were given

由報錯信息可知,由于調用時缺少參數名age和city,Python解釋器把這4個參數均視為位置參數,但enrroll()函數僅接受2個位置參數。
注意:命名關鍵字參數可以有缺省值。
由于命名關鍵字參數city具有默認值,調用時可不傳入city參數:

def enroll(name, gender, *, age='18', city):
    print(name, gender, age, city)

>>> enroll('Jack','M',city='Beijing')
Jack M 18 Beijing

5.4.6 參數組合
現在我們知道python定義函數的參數類型有:
必選參數、默認參數、可變參數、關鍵字參數和命名關鍵字參數

在Python中定義函數,我們是可以組合使用這些參數的。但要注意的是,參數定義是有順序的,定義的順序必須是:必選參數、默認參數、可變參數、命名關鍵字參數和關鍵字參數。
比如定義一個函數,包含上述若干種參數:

def func(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

在函數調用的時候,Python解釋器自動按照參數位置和參數名把對應的參數傳進去。

>>> func(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> func(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> func(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> func(1, 2, 3, 'a', 'b', x=4)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 4}

另外對于任意函數,都可以通過類似 func(*args, **kw) 的形式調用它,無論它的參數是如何定義的。以一個元組(tuple)和字典(dict)為例:

>>> args = (1, 2, 3, 4)
>>> kw = {'x': 5}
>>> func(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'x': 5}

小結
Python的函數具有非常靈活的參數形態,既可以實現簡單的調用,又可以傳入非常復雜的參數。
默認參數一定要用不可變對象,如果是可變對象,運行會有邏輯錯誤!
要注意定義可變參數和關鍵字參數的語法:
args 是可變參數,args接收的是一個tuple;
kw 是關鍵字參數,kw接收的是一個dict。
以及調用函數時如何傳入可變參數和關鍵字參數的語法:
可變參數既可以直接傳入: func(1, 2, 3) ,又可以先組裝list或tuple,再通過*args 傳入: func(
(1, 2, 3)) ;
關鍵字參數既可以直接傳入: func(a=1, b=2) ,又可以先組裝dict,再通過 **kw 傳入: func(
{'a': 1, 'b': 2}) 。
使用 *args**kw 是Python的習慣寫法,當然也可以用其他參數名,但最好使用習慣用法

5.6 返回值
函數并非總是直接顯示輸出,相反,它可以處理一些數據,并返回一個或一組值。函數返回的值被稱為返回值 。在函數中,可使用return 語句將值返回到調用函數的代碼行。返回值讓你能夠將程序的大部分繁重工作移到函數中去完成,從而簡化主程序。
return [表達式] 語句用于退出函數,選擇性地向調用方返回一個表達式。不帶參數值的return語句返回None。以下實例演示了 return 語句的用法:

#!/usr/bin/python3

def sum( arg1, arg2 ):
   total = arg1 + arg2       # 返回2個參數的和
   print ("函數內 : ", total)
   return total;

total = sum( 10, 20 );
print ("函數外 : ", total)
函數內 :  30
函數外 :  30

5.7 函數式編程
函數式編程是一種編程范式,我們常見的編程范式有命令式編程、函數式編程、邏輯式編程,常見的面向對象編程也是一種命令式編程。命令式編程是面向計算機硬件的抽象,在計算機的層次上,CPU執行的是加減乘除的指令代碼,以及各種條件判斷和跳轉指令。而函數式編程是面向數學的抽象,將計算描述為一種表達式求值,越是抽象的計算,離計算機硬件越遠。
值得注意的是,函數式編程中的“函數”不是指計算機中的函數,而是指數學中的函數,即自變量的映射,一個函數的值僅決定于函數參數的值,不依賴其它狀態。在編程式語言中,“函數”可以在任何地方定義,在函數內或函數外,可以作為函數的參數或返回值,可以對函數進行組合。純函數式編程中的變量也不是命令式編程語言中的變量,而是代數中的變量,即一個值的名稱,變量的值是不可變的,比如命令式編程中的"x = x+1"這種依賴可變狀態的事實此時被認為為假。
例:通過別的名稱使用函數,再把函數作為參數傳遞

>>> fact = factorial
>>> fact
<function factorial at  >
>>> func(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'x': 5}

函數式編程最主要的好處主要是不可變性帶來的,函數是“引用透明”的,并可避免“副作用”,它不會依賴也不會改變當前函數以外的數據。

高階函數
接受函數為參數,或者把函數作為結果返回的函數是高階函數(higher-
order function)。map 函數就是一例,此外,內置函數 sorted 也是:可選的 key 參數用于提供一個函數,它會應用到各個元素上進行排序.
例,若想根據單詞的長度排序,只需把 len 函數傳給 key 參數,如下:
根據單詞長度給一個列表排序

>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=len)
['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry'] 

任何單參數函數都能作為 key 參數的值。例如,為了創建押韻詞典,可以把各個單詞反過來拼寫,然后排序。注意,示例 5-4 中列表里的單詞沒有變,我們只是把反向拼寫當作排序條件,因此各種漿果(berry)都排在一起。
示例:根據反向拼寫給一個單詞列表排序

>>> def reverse(word):
...     return word[::-1]
>>> reverse('testing')
'gnitset'
>>> sorted(fruits, key=reverse)
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry'] 

在函數式編程范式中,最為人熟知的高階函數有map、filter、reduce 和 apply。apply 函數在 Python 2.3 中標記為過時,在 Python 3 中移除了,因為不再需要它了。如果想使用不定量的參數調用函數,可以編寫 fn(*args, **keywords),不用再編寫 apply(fn, args, kwargs)。

5.5 匿名函數
所謂匿名,意即不再使用 def 語句這樣標準的形式定義一個函數。python 使用 lambda 來創建匿名函數。
lambda 只是一個表達式,函數體比 def 簡單很多。lambda 函數的語法如下:

lambda [arg1 [,arg2,.....argn]]:expression

lambda的主體是一個表達式,而不是一個代碼塊,僅能在lambda表達式中封裝有限的邏輯進去。
注意:lambda 函數擁有自己的命名空間,且不能訪問自己參數列表之外或全局命名空間里的參數。
注意:雖然lambda函數看起來只能寫一行,卻不等同于C或C++的內聯函數,后者的目的是調用小函數時不占用棧內存從而增加運行效率。
如下實例:

#!/usr/bin/python3
 
sum = lambda arg1, arg2: arg1 + arg2;
 
# 調用sum函數
print ("相加后的值為 : ", sum( 1, 2 ))
print ("相加后的值為 : ", sum( 2, 2 ))
相加后的值為 :  3
相加后的值為 :  4

變量作用域
Python 中,程序的變量并不是在哪個位置都可以訪問的,訪問權限決定于這個變量是在哪里賦值的。
變量的作用域決定了在哪一部分程序可以訪問哪個特定的變量名稱。Python的作用域一共有4種,分別是:
L (Local) 局部作用域
E (Enclosing) 閉包函數外的函數中
G (Global) 全局作用域
B (Built-in) 內建作用域
以 L –> E –> G –>B 的規則查找,即:在局部找不到,便會去局部外的局部找(例如閉包),再找不到就會去全局找,再者去內建中找。

x = int(2.9)  # 內建作用域
 
g_count = 0  # 全局作用域
def outer():
    o_count = 1  # 閉包函數外的函數中
    def inner():
        i_count = 2  # 局部作用域

Python 中只有模塊(module),類(class)以及函數(def、lambda)才會引入新的作用域,其它的代碼塊(如 if/elif/else/、try/except、for/while等)是不會引入新的作用域的,也就是說這這些語句內定義的變量,外部也可以訪問,如下代碼:

>>> if True:
...  msg = 'I am from Runoob'
... 
>>> msg
'I am from Runoob'

實例中 msg 變量定義在 if 語句塊中,但外部還是可以訪問的。
如果將 msg 定義在函數中,則它就是局部變量,外部不能訪問:

>>> def test():
...     msg_inner = 'I am from Runoob'
... 
>>> msg_inner
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'msg_inner' is not defined

從報錯的信息上看,說明了 msg_inner 未定義,無法使用,因為它是局部變量,只有在函數內可以使用。
全局變量和局部變量
定義在函數內部的變量擁有一個局部作用域,定義在函數外的擁有全局作用域。
局部變量只能在其被聲明的函數內部訪問,而全局變量可以在整個程序范圍內訪問。調用函數時,所有在函數內聲明的變量名稱都將被加入到作用域中。如下實例:

#!/usr/bin/python3

total = 0; # 這是一個全局變量
# 可寫函數說明
def sum( arg1, arg2 ):
    #返回2個參數的和."
    total = arg1 + arg2; # total在這里是局部變量.
    print ("函數內是局部變量 : ", total)
    return total;

#調用sum函數
sum( 10, 20 );
print ("函數外是全局變量 : ", total)
函數內是局部變量 :  30
函數外是全局變量 :  0

global 和 nonlocal關鍵字
當內部作用域想修改外部作用域的變量時,就要用到global和nonlocal關鍵字了。
以下實例修改全局變量 num:

#!/usr/bin/python3

num = 1
def fun1():
    global num  # 需要使用 global 關鍵字聲明
    print(num) 
    num = 123
    print(num)
fun1()
1
123

如果要修改嵌套作用域(enclosing 作用域,外層非全局作用域)中的變量則需要 nonlocal 關鍵字了,如下實例:

#!/usr/bin/python3
 
def outer():
    num = 10
    def inner():
        nonlocal num   # nonlocal關鍵字聲明
        num = 100
        print(num)
    inner()
    print(num)
outer()
100
100

另外有一種特殊情況,假設下面這段代碼被運行:

#!/usr/bin/python3
 
a = 10
def test():
    a = a + 1
    print(a)
test()

以上程序執行,報錯信息如下:

Traceback (most recent call last):
  File "test.py", line 7, in <module>
    test()
  File "test.py", line 5, in test
    a = a + 1
UnboundLocalError: local variable 'a' referenced before assignment

錯誤信息為局部作用域引用錯誤,因為 test 函數中的 a 使用的是局部,未定義,無法修改。

全局變量和局部變量
定義在函數內部的變量擁有一個局部作用域,定義在函數外的擁有全局作用域。
局部變量只能在其被聲明的函數內部訪問,而全局變量可以在整個程序范圍內訪問。調用函數時,所有在函數內聲明的變量名稱都將被加入到作用域中。如下實例:

#!/usr/bin/python3

total = 0 # 這是一個全局變量
# 可寫函數說明
def sum( arg1, arg2 ):
    #返回2個參數的和."
    total = arg1 + arg2 # total在這里是局部變量.
    print ("函數內是局部變量 : ", total)
    return total

#調用sum函數
sum( 10, 20 )
print ("函數外是全局變量 : ", total)
以上實例輸出結果:
函數內是局部變量 :  30
函數外是全局變量 :  0
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容