函數一詞來源於數學,但編程中的「函數」概念,與數學中的函數是有很大不一樣的,具體區別,咱們後面會講,編程中的函數在英文中也有不少不一樣的叫法。在BASIC中叫作subroutine(子過程或子程序),在Pascal中叫作procedure(過程)和function,在C中只有function,在Java裏面叫作method。html
函數能提升應用的模塊性,和代碼的重複利用率。你已經知道Python提供了許多內建函數,好比print()。但你也能夠本身建立函數,這被叫作用戶自定義函數。python
定義: 函數是指將一組語句的集合經過一個名字(函數名)封裝起來,要想執行這個函數,只需調用其函數名便可程序員
特性:面試
1.代碼重用編程
2.保持一致性數組
3.可擴展性數據結構
Python 定義函數使用 def 關鍵字,通常格式以下:閉包
1
2
|
def
函數名(參數列表):
函數體
|
def hello():
print('hello')
hello()#調用
形參:形式參數,不是實際存在,是虛擬變量。在定義函數和函數體的時候使用形參,目的是在函數調用時接收實參(實參個數,類型應與實參一一對應)編程語言
實參:實際參數,調用函數時傳給函數的參數,能夠是常量,變量,表達式,函數,傳給形參 ide
區別:形參是虛擬的,不佔用內存空間,.形參變量只有在被調用時才分配內存單元,實參是一個變量,佔用內存空間,數據傳送單向,實參傳給形參,不能形參傳給實參
1
2
3
4
5
|
import
time
times
=
time.strftime(
'%Y--%m--%d'
)
def
f(time):
print
(
'Now time is : %s'
%
times)
f(times)
|
實例1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def
show_shopping_car():
saving
=
1000000
shopping_car
=
[
(
'Mac'
,
9000
),
(
'kindle'
,
800
),
(
'tesla'
,
100000
),
(
'Python book'
,
105
),
]
print
(
'您已經購買的商品以下'
.center(
50
,
'*'
))
for
i ,v
in
enumerate
(shopping_car,
1
):
print
(
'\033[35;1m %s: %s \033[0m'
%
(i,v))
expense
=
0
for
i
in
shopping_car:
expense
+
=
i[
1
]
print
(
'\n\033[32;1m您的餘額爲 %s \033[0m'
%
(saving
-
expense))
show_shopping_car()
|
實例2:
如今咱們就用一個例子來講明函數的三個特性:
def action1(n): print ('starting action1...') with open('日誌記錄','a') as f: f.write('end action%s\n'%n) def action2(n): print ('starting action2...') with open('日誌記錄','a') as f: f.write('end action%s\n'%n) def action3(n): print ('starting action3...') with open('日誌記錄','a') as f: f.write('end action%s\n'%n) action1(1) action2(2) action3(3) ##***************代碼重用 def logger(n): with open('日誌記錄','a') as f: f.write('end action%s\n'%n) def action1(): print ('starting action1...') logger(1) def action2(): print ('starting action2...') logger(2) def action3(): print ('starting action3...') logger(3) action1() action2() action3() ##***************可擴展和保持一致 ##爲日誌加上時間 import time def logger(n): time_format='%Y-%m-%d %X' time_current=time.strftime(time_format) with open('日誌記錄','a') as f: f.write('%s end action%s\n'%(time_current,n)) def action1(): print ('starting action1...') logger(1) def action2(): print ('starting action2...') logger(2) def action3(): print ('starting action3...') logger(3) action1() action2() action3()
1
2
3
4
5
6
|
def
f(name,age):
print
(
'I am %s,I am %d'
%
(name,age))
f(
'alex'
,
18
)
f(
'alvin'
,
16
)
|
關鍵字參數和函數調用關係緊密,函數調用使用關鍵字參數來肯定傳入的參數值。使用關鍵字參數容許函數調用時參數的順序與聲明時不一致,由於 Python 解釋器可以用參數名匹配參數值。
1
2
3
4
5
6
|
def
f(name,age):
print
(
'I am %s,I am %d'
%
(name,age))
# f(16,'alvin') #報錯
f(age
=
16
,name
=
'alvin'
)
|
調用函數時,缺省參數的值若是沒有傳入,則被認爲是默認值。下例會打印默認的age,若是age沒有被傳入:
1
2
3
4
5
6
7
8
9
|
def
print_info(name,age,sex
=
'male'
):
print
(
'Name:%s'
%
name)
print
(
'age:%s'
%
age)
print
(
'Sex:%s'
%
sex)
return
print_info(
'alex'
,
18
)
print_info(
'鐵錘'
,
40
,
'female'
)
|
你可能須要一個函數能處理比當初聲明時更多的參數。這些參數叫作不定長參數,和上述2種參數不一樣,聲明時不會命名。
1
2
3
4
5
6
7
8
9
10
11
12
|
# def add(x,y):
# return x+y
def
add(
*
tuples):
sum
=
0
for
v
in
tuples:
sum
+
=
v
return
sum
print
(add(
1
,
4
,
6
,
9
))
print
(add(
1
,
4
,
6
,
9
,
5
))
|
加了星號(*)的變量名會存放全部未命名的變量參數。而加(**)的變量名會存放命名的變量參數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
def
print_info(
*
*
kwargs):
print
(kwargs)
for
i
in
kwargs:
print
(
'%s:%s'
%
(i,kwargs[i]))
#根據參數能夠打印任意相關信息了
return
print_info(name
=
'alex'
,age
=
18
,sex
=
'female'
,hobby
=
'girl'
,nationality
=
'Chinese'
,ability
=
'Python'
)
###########################位置
def
print_info(name,
*
args,
*
*
kwargs):
#def print_info(name,**kwargs,*args):報錯
print
(
'Name:%s'
%
name)
print
(
'args:'
,args)
print
(
'kwargs:'
,kwargs)
return
print_info(
'alex'
,
18
,hobby
=
'girl'
,nationality
=
'Chinese'
,ability
=
'Python'
)
# print_info(hobby='girl','alex',18,nationality='Chinese',ability='Python') #報錯
#print_info('alex',hobby='girl',18,nationality='Chinese',ability='Python') #報錯
|
注意,還能夠這樣傳參:
1
2
3
4
5
6
7
8
9
|
def
f(
*
args):
print
(args)
f(
*
[
1
,
2
,
5
])
def
f(
*
*
kargs):
print
(kargs)
f(
*
*
{
'name'
:
'alex'
})
|
補充(高階函數):
高階函數是至少知足下列一個條件的函數:
1
2
3
4
5
6
7
8
9
10
11
|
def
add(x,y,f):
return
f(x)
+
f(y)
res
=
add(
3
,
-
6
,
abs
)
print
(res)
###############
def
foo():
x
=
3
def
bar():
return
x
return
bar
|
要想獲取函數的執行結果,就能夠用return語句把結果返回
注意:
python中的做用域分4種狀況:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
x
=
int
(
2.9
)
# int built-in
g_count
=
0
# global
def
outer():
o_count
=
1
# enclosing
def
inner():
i_count
=
2
# local
print
(o_count)
# print(i_count) 找不到
inner()
outer()
# print(o_count) #找不到
|
固然,local和enclosing是相對的,enclosing變量相對上層來講也是local。
在Python中,只有模塊(module),類(class)以及函數(def、lambda)纔會引入新的做用域,其它的代碼塊(如if、try、for等)是不會引入新的做用域的,以下代碼:
1
2
3
|
if
2
>
1
:
x
=
1
print
(x)
# 1
|
這個是沒有問題的,if並無引入一個新的做用域,x仍處在當前做用域中,後面代碼可使用。
1
2
3
|
def
test():
x
=
2
print
(x)
# NameError: name 'x2' is not defined
|
def、class、lambda是能夠引入新做用域的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#################
x
=
6
def
f2():
print
(x)
x
=
5
f2()
# 錯誤的緣由在於print(x)時,解釋器會在局部做用域找,會找到x=5(函數已經加載到內存),但x使用在聲明前了,因此報錯:
# local variable 'x' referenced before assignment.如何證實找到了x=5呢?簡單:註釋掉x=5,x=6
# 報錯爲:name 'x' is not defined
#同理
x
=
6
def
f2():
x
+
=
1
#local variable 'x' referenced before assignment.
f2()
|
當內部做用域想修改外部做用域的變量時,就要用到global和nonlocal關鍵字了,當修改的變量是在全局做用域(global做用域)上的,就要使用global先聲明一下,代碼以下:
1
2
3
4
5
6
7
8
9
|
count
=
10
def
outer():
global
count
print
(count)
count
=
100
print
(count)
outer()
#10
#100
|
global關鍵字聲明的變量必須在全局做用域上,不能嵌套做用域上,當要修改嵌套做用域(enclosing做用域,外層非全局做用域)中的變量怎麼辦呢,這時就須要nonlocal關鍵字了
1
2
3
4
5
6
7
8
9
10
11
|
def
outer():
count
=
10
def
inner():
nonlocal count
count
=
20
print
(count)
inner()
print
(count)
outer()
#20
#20
|
(1)變量查找順序:LEGB,做用域局部>外層做用域>當前模塊中的全局>python內置做用域;
(2)只有模塊、類、及函數才能引入新做用域;
(3)對於一個變量,內部做用域先聲明就會覆蓋外部變量,不聲明直接使用,就會使用外部做用域的變量;
(4)內部做用域要修改外部做用域變量的值時,全局變量要使用global關鍵字,嵌套做用域變量要使用nonlocal關鍵字。nonlocal是python3新增的關鍵字,有了這個 關鍵字,就能完美的實現閉包了。
定義:在函數內部,能夠調用其餘函數。若是一個函數在內部調用自身自己,這個函數就是遞歸函數。
實例1(階乘)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
def
factorial(n):
result
=
n
for
i
in
range
(
1
,n):
result
*
=
i
return
result
print
(factorial(
4
))
#**********遞歸*********
def
factorial_new(n):
if
n
=
=
1
:
return
1
return
n
*
factorial_new(n
-
1
)
print
(factorial_new(
3
))
|
實例2(斐波那契數列)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
def
fibo(n):
before
=
0
after
=
1
for
i
in
range
(n
-
1
):
ret
=
before
+
after
before
=
after
after
=
ret
return
ret
print
(fibo(
3
))
#**************遞歸*********************
def
fibo_new(n):
#n能夠爲零,數列有[0]
if
n <
=
1
:
return
n
return
(fibo_new(n
-
1
)
+
fibo_new(n
-
2
))
print
(fibo_new(
3
))
|
1
|
print
(fibo_new(
30000
))
#maximum recursion depth exceeded in comparison
|
遞歸函數的優勢: 是定義簡單,邏輯清晰。理論上,全部的遞歸函數均可以寫成循環的方式,但循環的邏輯不如遞歸清晰。
遞歸特性:
1. 必須有一個明確的結束條件
2. 每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減小
3. 遞歸效率不高,遞歸層次過多會致使棧溢出(在計算機中,函數調用是經過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返 回,棧就會減一層棧幀。因爲棧的大小不是無限的,因此,遞歸調用的次數過多,會致使棧溢出。)
py2內置函數:https://docs.python.org/3.5/library/functions.html#repr
重要的內置函數:
1 filter(function, sequence)
1
2
3
4
5
6
7
8
9
10
|
str
=
[
'a'
,
'b'
,
'c'
,
'd'
]
def
fun1(s):
if
s !
=
'a'
:
return
s
ret
=
filter
(fun1,
str
)
print
(
list
(ret))
# ret是一個迭代器對象
|
對sequence中的item依次執行function(item),將執行結果爲True的item作成一個filter object的迭代器返回。能夠看做是過濾函數。
2 map(function, sequence)
1
2
3
4
5
6
7
8
9
10
|
str
=
[
1
,
2
,
'a'
,
'b'
]
def
fun2(s):
return
s
+
"alvin"
ret
=
map
(fun2,
str
)
print
(ret)
# map object的迭代器
print
(
list
(ret))
# ['aalvin', 'balvin', 'calvin', 'dalvin']
|
對sequence中的item依次執行function(item),將執行結果組成一個map object迭代器返回.
map也支持多個sequence,這就要求function也支持相應數量的參數輸入:
1
2
3
|
ef add(x,y):
return
x
+
y
print
(
list
(
map
(add,
range
(
10
),
range
(
10
))))
##[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
|
3 reduce(function, sequence, starting_value)
1
2
3
4
5
6
7
8
|
from
functools
import
reduce
def
add1(x,y):
return
x
+
y
print
(
reduce
(add1,
range
(
1
,
101
)))
## 4950 (注:1+2+...+99)
print
(
reduce
(add1,
range
(
1
,
101
),
20
))
## 4970 (注:1+2+...+99+20)
|
對sequence中的item順序迭代調用function,若是有starting_value,還能夠做爲初始值調用.
4 lambda
普通函數與匿名函數的對比:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#普通函數
def
add(a,b):
return
a
+
b
print
add(
2
,
3
)
#匿名函數
add
=
lambda
a,b : a
+
b
print
add(
2
,
3
)
#========輸出===========
5
5
|
匿名函數的命名規則,用lamdba 關鍵字標識,冒號(:)左側表示函數接收的參數(a,b) ,冒號(:)右側表示函數的返回值(a+b)。
由於lamdba在建立時不須要命名,因此,叫匿名函數
學會了上面幾個重要的函數後,咱們就能夠來聊一聊函數式編程究竟是個什麼鬼
函數式編程是一種編程範式,咱們常見的編程範式有命令式編程(Imperative programming),函數式編程,常見的面向對象編程是也是一種命令式編程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#計算數組中正整數的平均值
number
=
[
2
,
-
5
,
9
,
-
7
,
2
,
5
,
4
,
-
1
,
0
,
-
3
,
8
]
count
=
0
sum
=
0
for
i
in
range
(
len
(number)):
if
number[i]>
0
:
count
+
=
1
sum
+
=
number[i]
print
sum
,count
if
count>
0
:
average
=
sum
/
count
print
average
#========輸出===========
30
6
5
|
首先循環列表中的值,累計次數,並對大於0的數進行累加,最後求取平均值。
這就是命令式編程——你要作什麼事情,你得把達到目的的步驟詳細的描述出來,而後交給機器去運行。
這也正是命令式編程的理論模型——圖靈機的特色。一條寫滿數據的紙帶,一條根據紙帶內容運動的機器,機器每動一步都須要紙帶上寫着如何達到。
那麼,不用這種方式如何作到呢?
1
2
3
4
5
6
7
8
9
10
|
number
=
[
2
,
-
5
,
9
,
-
7
,
2
,
5
,
4
,
-
1
,
0
,
-
3
,
8
]
positive
=
filter
(
lambda
x: x>
0
, number)
average
=
reduce
(
lambda
x,y: x
+
y, positive)
/
len
(positive)
print
average
#========輸出===========
5
|
這段代碼最終達到的目的一樣是求取正數平均值,可是它獲得結果的方式和 以前有着本質的差異:經過描述一個列表->正數平均值 的映射,而不是描述「從列表獲得正數平均值應該怎樣作」來達到目的。
再好比,求階乘
經過Reduce函數加lambda表達式式實現階乘是如何簡單:
1
2
|
from
functools
import
reduce
print
(
reduce
(
lambda
x,y: x
*
y,
range
(
1
,
6
)))
|
又好比,map()函數加上lambda表達式(匿名函數)能夠實現更強大的功能:
1
2
3
|
squares
=
map
(
lambda
x : x
*
x ,
range
(
9
))
print
(squares)
# <map object at 0x10115f7f0>迭代器
print
(
list
(squares))
#[0, 1, 4, 9, 16, 25, 36, 49, 64]
|
三 函數式編程有什麼好處呢?
1)代碼簡潔,易懂。
2)無反作用
因爲命令式編程語言也能夠經過相似函數指針的方式來實現高階函數,函數式的最主要的好處主要是不可變性帶來的。沒有可變的狀態,函數就是引用透明(Referential transparency)的和沒有反作用(No Side Effect)。