前面幾章已經學習了 Python 內置的一些函數,如 `print()` `input()` `len()` `str()` 等。其實我們自己也可以編寫屬于自己的函數。函數其實是可重復使用的一系列代碼的集合,或者說是可重復使用的代碼塊,顯然函數也是一種流程控制結構。
*****
[TOC]
****
# 4.1 代數中的函數
代數中,一個函數如: `f(x) = x * 2`
這是一個非常簡單的函數,用以求給定 x 的2倍。如:f(2) = 2 * 2 = 4,f(3) = 3 * 2 = 6。
# 4.2 Python中函數定義與使用
**定義** Python中函數使用關鍵字 `def` 來定義,語法如下:
```
def function_name(parameters):
''' docstring '''
statement(s)
```
中文解釋:
```
def 函數名(參數們):
''' 文檔字符串,描述函數是做什么的 '''
函數體(一行以上的代碼)
```
函數名命名規則同變量名命名規則。
**使用定義好的函數——調用**
```
function_name(actual parameters)
```
實例:
```
# 斐波那契數列
def fib(n):
"""print a Fibbonacci series up to n."""
a, b = 0, 1
fibLst = []
while a < n:
## print(a, end=',')
fibLst.append(a)
a, b = b, a+b
return fibLst
print(fib(100))
```
```
# Out:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
```
該實例定義了 fib(n) 函數,n 稱之為形式參數(形參)。 `print(fib(100))` 中 fib(100) 則是對已定義函數的調用,100 稱之為實際參數(實參)。
# 4.3 函數的參數
*參考runobb.com*
**可更改(mutable)與不可更改(immutable)對象:**
在 Python 中,strings, tuples, 和 numbers 是不可更改的對象,而 list,dict 等則是可以修改的對象。
* **不可變類型:**變量賦值**a=5**后再賦值**a=10**,這里實際是新生成一個 int 值對象 10,再讓 a 指向它,而 5 被丟棄,不是改變a的值,相當于新生成了a。
* **可變類型:**變量賦值**la=[1,2,3,4]**后再賦值**la[2]=5**則是將 list la 的第三個元素值更改,本身la沒有動,只是其內部的一部分值被修改了。
Python 函數的參數傳遞:
* **不可變類型:**類似 c++ 的值傳遞,如 整數、字符串、元組。如fun(a),傳遞的只是a的值,沒有影響a對象本身。比如在 fun(a)內部修改 a 的值,只是修改另一個復制的對象,不會影響 a 本身。
* **可變類型:**類似 c++ 的引用傳遞,如 列表,字典。如 fun(la),則是將 la 真正的傳過去,修改后fun外部的la也會受影響
Python 中一切都是對象,嚴格意義我們不能說值傳遞還是引用傳遞,我們應該說傳不可變對象和傳可變對象。
Python 傳不可變對象實例:
```
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 指向它。
Python 傳可變對象實例:
可變對象在函數里修改了參數,那么在調用這個函數的函數里,原始的參數也被改變了。例如:
```
# 可寫函數說明
def changeme(mylist):
"'修改傳入的列表"'
mylist.append([1,2,3,4])
print("函數內取值: ", mylist)
return
# 調用changeme函數
mylist = [10,20,30]
changeme(mylist)
print("函數外取值: ", mylist)
```
```
# Out:
函數內取值: [10, 20, 30, [1, 2, 3, 4]]
函數外取值: [10, 20, 30, [1, 2, 3, 4]]
```
**調用函數時可使用的正式參數類型**(若定義時參數為空—即括號里為空,則調用時不需要傳入參數):
* 必需參數
* 關鍵字參數
* 默認參數
* 不定長參數
必需參數:
必需參數須以正確的順序傳入函數。調用時的數量必須和聲明時的一樣。如下:
```
# 可寫函數說明
def printme(str):
'''打印任何傳入的字符串'''
print(str)
return
# 調用 printme 函數,不加參數會報錯
printme()
```
```
# Out:
Traceback (most recent call last):
File "function-para.py", line 8, in <module>
printme()
TypeError: printme() missing 1 required positional argument: 'str'
```
關鍵字參數:
關鍵字參數和函數調用關系緊密,函數調用使用關鍵字參數來確定傳入的參數值。
使用關鍵字參數允許函數調用時參數的順序與聲明時不一致,因為 Python 解釋器能夠用參數名匹配參數值。
以下實例在函數 printme() 調用時使用參數名:
```
# 可寫函數說明
def printme(str):
'''打印任何傳入的字符串'''
print(str)
return
# 調用printme函數
printme(str = "隨心而碼") # 隨心而碼
```
以下實例中演示了函數參數的使用不需要使用指定順序:
```python
#可寫函數說明
def printinfo( name, age ):
"打印任何傳入的字符串"
print ("名字: ", name)
print ("年齡: ", age)
return
#調用printinfo函數
printinfo( age=50, name="runoob" )
```
```
# Out:
名字: runoob
年齡: 50
```
默認參數:
調用函數時,如果沒有傳遞參數,則會使用默認參數。以下實例中如果沒有傳入 age 參數,則使用默認值:
```
#可寫函數說明
def printinfo( name, age = 35 ):
"打印任何傳入的字符串"
print ("名字: ", name)
print ("年齡: ", age)
return
#調用printinfo函數
printinfo( age=50, name="runoob" )
print ("------------------------")
printinfo( name="runoob" )
```
```
# Out:
名字: runoob
年齡: 50
------------------------
名字: runoob
年齡: 35
```
不定長參數:
你可能需要一個函數能處理比當初聲明時更多的參數。這些參數叫做不定長參數,和上述 2 種參數不同,聲明時不會命名。基本語法如下:
```
def functionname([formal_args,] *var_args_tuple ):
"函數_文檔字符串"
function_suite
return [expression]
```
加了星號\*的參數會以元組(tuple)的形式導入,存放所有未命名的變量參數。
```
# 可寫函數說明
def printinfo( arg1, *vartuple ):
"打印任何傳入的參數"
print ("輸出: ")
print (arg1)
print (vartuple)
# 調用printinfo 函數
printinfo( 70, 60, 50 )
```
```
# Out:
70
(60, 50)
```
如果在函數調用時沒有指定參數,它就是一個空元組。我們也可以不向函數傳遞未命名的變量。如下實例:
```
# 可寫函數說明
def printinfo( arg1, *vartuple ):
"打印任何傳入的參數"
print ("輸出: ")
print (arg1)
for var in vartuple:
print (var)
return
# 調用printinfo 函數
printinfo( 10 )
printinfo( 70, 60, 50 )
```
```
# Out:
10
輸出:
70
60
50
```
還有一種就是參數帶兩個星號\*\*基本語法如下:
```
def functionname([formal_args,] **var_args_dict ):
"函數_文檔字符串"
function_suite
return [expression]
```
加了兩個星號\*\*的參數會以字典的形式導入。
```
# 可寫函數說明
def printinfo( arg1, **vardict ):
"打印任何傳入的參數"
print ("輸出: ")
print (arg1)
print (vardict)
# 調用printinfo 函數
printinfo(1, a=2,b=3)
```
```
# Out:
1
{'a': 2, 'b': 3}
```
*****
# 4.4 函數與變量作用域
前面已經學習過了變量及其命名規則,現在學習變量作用域——變量的作用范圍。只有在模塊、函數、類中,我們討論變量的作用域。
**全局變量與局部變量**
在 Python 中根據變量可供訪問的作用范圍,將變量分為全局變量(Global Variable)和局部變量(Local Variable)。
* 全局變量定義于模塊、函數、類之外,自賦值定義開始,后續代碼都可以訪問該變量
* 局部變量只能在被定義的模塊、函數或類內部被訪問
```
j = 10 # Global Variable
def add(i):
i = i + j # i: Local Variable
return i
i = 16 # Global Variable,not the i inside the function
print('Global Variable:', j)
print('"i" inside the function:', add(5))
print('"i" outside the function,it\'s a new global variable:', i)
```
```
# Out:
Global Variable: 10
"i" inside the function: 15
"i" outside the function,it's a new global variable: 16
```
**global 關鍵字**
```
i = 1
def test():
i = i + 5
print(i)
test()
```
```
# Out:
Traceback (most recent call last):
File "func-variable-scope.py", line 18, in <module>
test()
File "func-variable-scope.py", line 15, in test
i = i + 5
UnboundLocalError: local variable 'i' referenced before assignment
```
添加 `global` 關鍵字后,內部作用域可修改外部作用域
```
i = 1
print('全局變量內存地址:',id(i))
def test():
global i
print('全局變量函數內賦值前內存地址:',id(i))
i = i + 5
print('全局變量函數內賦值后內存地址:',id(i))
print(i)
test()
print(i)
```
```
# Out:
全局變量內存地址: 140712509232160
全局變量函數內賦值前內存地址: 140712509232160
全局變量函數內賦值后內存地址: 140712509232320
6
6
```
顯然(哈哈哈哈哈···),最初定義的全局變量內存地址不變。在函數內賦值后的 i 是一個新的全局變量。
**閉包(Closure)** 閉包是介于全局變量和局部變量之間的一種特殊變量。閉包變量定義在外部函數與內部嵌套函數之間。用法如下:
```
j = 5 # 全局變量 j
def sum0(): # 外部函數 sum0
k = 1 # 閉包變量 k
def sum1(): # 嵌套的內部函數 sum1
i = k + j # 局部變量 i
return i
return sum1()
s = sum0()
print(s) # 輸出 6
```
**nonlocal 關鍵字**
如果要修改嵌套作用域(Closure作用域,外層非全局作用域)中的變量則需要 nonlocal 關鍵字了,如下實例:
```
def outer():
num = 10
def inner():
nonlocal num # nonlocal關鍵字聲明
num = 100
print(num)
inner()
print(num)
outer()
```
```
# Out:
100
100
```
全局變量、閉包變量、局部變量的使用范圍的關系如下:
全局變量 > 閉包變量 > 局部變量。
注意:在編程中盡量不要使用這些編程方法。
*****
# 4.5 匿名函數
Python 中使用 `lambda` 來創建匿名函數。所謂匿名函數,與 `def` 關鍵字定義的函數相比,沒有函數名稱。
* lambda 只是一個表達式,函數體比 def 簡單很多。
* lambda 的主體是一個表達式,而不是一個代碼塊。僅僅能在 lambda 表達式中封裝有限的邏輯進去。
* lambda 函數擁有自己的命名空間,且不能訪問自己參數列表之外或全局命名空間里的參數。
* 雖然 lambda 函數看起來只能寫一行,卻不等同于C或C++的內聯函數,后者的目的是調用小函數時不占用棧內存從而增加運行效率。
lambda 函數的語法只包含一個語句,如下:
```
lambda [arg1 [,arg2,.....argn]]:expression
```
示例:
```
>>> lambda x, y: x * y
<function <lambda> at 0x000002116691C1E0>
>>> func = lambda x, y: x * y
>>> func(2,2)
4
```
*****
# 4.6 遞歸函數
遞歸(Recursion Algorithm,遞歸算法)在計算機科學中是指一種通過重復將問題分解為同類子問題而解決問題的方法。
利用函數實現遞歸過程就是遞歸函數。遞歸函數通過自己調用自己來實現遞歸算法。
實例:求 1,2,3,4,···,n 加法和
```
# 一般求和函數
def add(num):
i = 1
add = 0
while i <= num:
add = add + i
i += 1
return add
print(add(50)) # 1275
# 遞歸求和函數
def recursion_add(num):
if num == 1:
return num
return recursion_add(num-1) + num
print(recursion_add(50)) # 1275
```