函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不一样的,具体区别,咱们后面会讲,编程中的函数在英文中也有不少不一样的叫法。在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)。