函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可
函数的三个特性:
1.代码重用
2.保持一致性
3.可扩展性
一、函数的创建
1 def 函数名(参数列表): # 这里的参数是形参
2 函数体
3
4 def hello(name): # 这里的参数是形参
5 print('hello')
6
7 hello('wanstack')#调用 # 这里的参数是实参
形参和实参:
形参:形式参数,不是实际存在,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参(实参个数,类型应与实参一一对应)
实参:实际参数,调用函数时传给函数的参数,可以是常量,变量,表达式,函数,传给形参
区别:形参是虚拟的,不占用内存空间,.形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参
打印当前时间例子:
1 import time
2 time = time.strftime('%Y-%m-%d %H:%M:%S')
3 # print(time)
4
5 def fun(time):
6 print('The current time is {time}'.format(time=time))
7
8 fun(time)
购买商品例子:
1 def show_shopping_car():
2 saving = 1000000
3 shopping_car = [
4 ('Mac', 9000),
5 ('kindle', 800),
6 ('tesla', 100000),
7 ('Python book', 105),
8 ]
9 print('您已经购买的商品如下'.center(50, '*'))
10 for i, v in enumerate(shopping_car, 1):
11 print('\033[35;1m %s: %s \033[0m' % (i, v))
12
13 expense = 0
14 for i in shopping_car:
15 expense += i[1]
16 print('\n\033[32;1m您的余额为 %s \033[0m' % (saving - expense))
17
18
19 show_shopping_car()
使用下面的例子来说明函数的三个特性:
1 def action1(n):
2 print ('starting action1...')
3
4 with open('日志记录','a') as f:
5 f.write('end action%s\n'%n)
6
7 def action2(n):
8 print ('starting action2...')
9
10 with open('日志记录','a') as f:
11 f.write('end action%s\n'%n)
12
13 def action3(n):
14 print ('starting action3...')
15
16 with open('日志记录','a') as f:
17 f.write('end action%s\n'%n)
18
19 action1(1)
20 action2(2)
21 action3(3)
22
23
24 ##***************代码重用
25
26 def logger(n):
27 with open('日志记录','a') as f:
28 f.write('end action%s\n'%n)
29
30 def action1():
31 print ('starting action1...')
32 logger(1)
33
34
35 def action2():
36 print ('starting action2...')
37 logger(2)
38
39
40 def action3():
41 print ('starting action3...')
42 logger(3)
43
44
45 action1()
46 action2()
47 action3()
48
49 ##***************可扩展和保持一致
50 ##为日志加上时间
51 import time
52
53 def logger(n):
54 time_format='%Y-%m-%d %X'
55 time_current=time.strftime(time_format)
56
57 with open('日志记录','a') as f:
58 f.write('%s end action%s\n'%(time_current,n))
59
60 def action1():
61 print ('starting action1...')
62 logger(1)
63
64
65 def action2():
66 print ('starting action2...')
67 logger(2)
68
69
70 def action3():
71 print ('starting action3...')
72 logger(3)
73
74 action1()
75 action2()
76 action3()
函数的三个特性
二、函数的参数
- 必备参数
- 关键字参数
- 默认参数
- 不定长参数
2.1 必备参数
必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
1 def f(name, age):
2 print('I am %s,I am %d' % (name, age))
3
4
5 f('fish', 18)
6 f('wamstack', 16)
2.2 关键字参数
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
1 def f(name,age):
2
3 print('I am %s,I am %d'%(name,age))
4
5 # f(16,'fish') #报错
6 f(age=16,name='fish')
2.3 缺省参数(默认参数)
默认参数必须放在必需参数后面
调用函数时,缺省参数的值如果没有传入,则被认为是默认值。下例会打印默认的sex,如果sex没有被传入:
def print_info(name,age,sex='male'):
print('Name:%s'%name)
print('age:%s'%age)
print('Sex:%s'%sex)
return
print_info('fish',18)
print_info('铁锤',40,'female')
2.4 不定长参数
你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述2种参数不同,声明时不会命名。
1 def add(*args):
2 sum = 0
3 for i in args:
4 sum = sum + i
5 return sum
6 print(add(1,4,6,9))
加了星号(*)的变量名会存放所有未命名的变量参数。而加(**)的变量名会存放命名的变量参数
def fun(**kwags):
print(kwags)
fun(name='wanstack',age=18,sex='female',hobby='girl',nationality='Chinese',ability='Python')
# {'name': 'wanstack', 'age': 18, 'sex': 'female', 'hobby': 'girl', 'nationality': 'Chinese', 'ability': 'Python'}
# 结果是一个字典
当有混合参数的时候位置很重要
1 def print_info(name, *args, **kwargs): # def print_info(name,**kwargs,*args):报错
2 print('Name:%s' % name)
3 print('args:', args)
4 print('kwargs:', kwargs)
5 return
6 print_info('wanstack', 18, hobby='girl', nationality='Chinese', ability='Python')
7 # print_info(hobby='girl','alex',18,nationality='Chinese',ability='Python') #报错
8 # print_info('alex',hobby='girl',18,nationality='Chinese',ability='Python') #报错
9 """
10 Name:wanstack
11 args: (18,)
12 kwargs: {'hobby': 'girl', 'nationality': 'Chinese', 'ability': 'Python'}
13 """
混合参数
1 def fun('必须参数','默认参数',*args,**kwargs):
2 pass
还可以如下方式传参:
1 def f(*args):
2 print(args)
3 f(*[1, 2, 5]) # 这种方式传参最终会得到一个列表,如果是之前方式传参则会得到要给([1,2,3])这个传的列表是元组中的一个元素
4
5 def f(**kargs):
6 print(kargs)
7 f(**{'name': 'wanstack'}) # 这种方式传参可以直接传一个字典,而不是需要一个类似name_info={'name': 'wanstack'} 这种形式的参数。
高阶函数是至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入
- 输出一个函数
1 def add(x, y, f): # 回掉函数,函数作为参数
2 return f(x) + f(y)
3 res = add(3, -6, abs)
4 print(res)
5 ###############
6 def foo(): # 装饰器,函数作为返回值
7 x = 3
8 def bar():
9 return x
10 return bar
变量和函数的理解:
三、函数返回值
要想获取函数的执行结果,就可以用return语句把结果返回
注意:
- 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为 return 语句代表着函数的结束
- 如果未在函数中指定return,那这个函数的返回值为None
- return多个对象,解释器会把这多个对象组装成一个元组作为一个一个整体结果输出。
四、作用域
python作用于主要分为以下四种:查找的顺序依次为L-->E-->G-->B
- L:local,局部作用域,即函数中定义的变量;
- E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;
- G:globa,全局变量,就是模块级别定义的变量;
- B:built-in,系统固定模块里面的变量,比如int, bytearray等。 搜索变量的优先级顺序依次是:作用域局部>外层作用域>当前模块中的全局>python内置作用域,也就是LEGB。
1 x = int(2.9) # int built-in
2
3 g_count = 0 # global
4 def outer():
5 o_count = 1 # enclosing
6 def inner():
7 i_count = 2 # local
8 print(o_count)
9 # print(i_count) 找不到
10 inner()
11 outer()
12
13 # print(o_count) #找不到
当然,local和enclosing是相对的,enclosing变量相对上层来说也是local。
在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如if、try、for等)是不会引入新的作用域的
1 if 2>1:
2 x = 1
3 print(x) # 1
这个是没有问题的,if并没有引入一个新的作用域,x仍处在当前作用域中,后面代码可以使用。
1 def test():
2 x = 2
3 print(x) # NameError: name 'x2' is not defined
def、class、lambda是可以引入新作用域的。
4.1 变量的修改
1 #################
2 x=6
3 def f2():
4 print(x)
5 x=5
6 f2()
7
8 # 错误的原因在于print(x)时,解释器会在局部作用域找,会找到x=5(函数已经加载到内存),但x使用在声明前了,所以报错:
9 # local variable 'x' referenced before assignment.如何证明找到了x=5呢?简单:注释掉x=5,x=6
10 # 报错为:name 'x' is not defined
11 #同理
12 x=6
13 def f2():
14 x+=1 #local variable 'x' referenced before assignment.
15 f2()
4.2 global关键字
当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了,当修改的变量是在全局作用域(global作用域)上的,就要使用global先声明一下,代码如下:
1 count = 10
2 def outer():
3 global count
4 print(count)
5 count = 100
6 print(count)
7 outer()
8 #10
9 #100
4.3 nonlocal关键字
global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量怎么办呢,这时就需要nonlocal关键字了
1 def outer():
2 count = 10
3 def inner():
4 nonlocal count
5 count = 20
6 print(count)
7 inner()
8 print(count)
9 outer()
10 #20
11 #20
4.4 小结
(1)变量查找顺序:LEGB,作用域局部>外层作用域>当前模块中的全局>python内置作用域;
(2)只有模块、类、及函数才能引入新作用域;
(3)对于一个变量,内部作用域先声明就会覆盖外部变量,不声明直接使用,就会使用外部作用域的变量;
(4)内部作用域要修改外部作用域变量的值时,全局变量要使用global关键字,嵌套作用域变量要使用nonlocal关键字。nonlocal是python3新增的关键字,有了这个 关键字,就能完美的实现闭包了。
五、递归函数
定义:在函数内部,可以调用其他函数。如果一个函数在内部调用自身,这个函数就是递归函数。
# 使用递归函数实现阶乘: 例如: !10 = 10*!9 = 10*9*!8 ...
1 def factorial_new(n):
2 if n == 1:
3 return 1
4 return n * factorial_new(n - 1)
5
6 # 执行顺序
7 # 4*f(3)-->4*3*f(2)-->4*3*2*f(1)-->24
8 print(factorial_new(4))
9
10 # 执行结果24
1 # 斐波那契数列
2 # 0 1 1 2 3 5 8 13 21 34
3 # fact(8) = fact(7) + fact(6)
4 def fact(n):
5 if n <=2:
6 return n
7 return fact(n-1) + fact(n-2)
8
9 print(fact(7))
递归函数的优点: 是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
递归特性:
1. 必须有一个明确的结束条件
2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返 回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。)
六、内置函数(py3.5)
https://docs.python.org/3.5/library/functions.html
重要的内置函数
1、filter(function, sequence)
ret是一个迭代器对象
对sequence中的item(元素)依次执行function(item),将执行结果为True的item做成一个filter object的迭代器返回。可以看作是过滤函数。
2、 map(function, sequence)
1 str = ['1','2', 'a', 'b']
2 def fun2(s):
3 return (s + "alvin")
4
5 ret = map(fun2, str)
6
7 print(ret) # map object的迭代器
8 print(list(ret)) # ['1alvin', '2alvin', 'aalvin', 'balvin']
对sequence中的item依次执行function(item),将执行结果组成一个map object迭代器返回.
map也支持多个sequence,这就要求function也支持相应数量的参数输入:
1 def add(x,y):
2 return x+y
3 print (list(map(add, range(10), range(10))))##[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
4 # add(0,0),add(1,1),依次类推
def add(x,y):
return x,y
print (list(map(add, range(10), range(10)))) #[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]
3 reduce(function, sequence, starting_value)
1 def fun(a,b):
2 return a + b
3
4 from functools import reduce
5 ret = reduce(fun,range(1,101)) # 4050 (1+2+3...+100)
6 ret = reduce(fun,range(1,101),8) # 4058 (1+2+3...+100+8)
7 print(ret)
def add(x,y):
return x,y
from functools import reduce
ret = reduce(add,range(10))
print(ret) # (((((((((0, 1), 2), 3), 4), 5), 6), 7), 8), 9)
# 先从range(10)中拿出0,1放到函数中,然后得出的结果作为函数的一个参数,然后再从range(10)中依次拿一个2作为函数的另一个参数
# 得出的结果作为函数的一个参数....依次类推
对sequence中的item顺序迭代调用function,如果有starting_value,还可以作为初始值调用.
4、 lambda
1 fun = lambda a,b: a+b # fun是函数名, 参数:返回值
2 # 等价于
3 def fun(a,b):
4 return a+b
匿名函数的命名规则,用lamdba 关键字标识,冒号(:)左侧表示函数接收的参数(a,b) ,冒号(:)右侧表示函数的返回值(a+b)。
因为lamdba在创建时不需要命名,所以,叫匿名函数
七、函数式编程
学会了上面几个重要的函数后,我们就可以来聊一聊函数式编程到底是个什么鬼
一 概念(函数式编程)
函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,常见的面向对象编程是也是一种命令式编程。
命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机的指令序列。
而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式。
函数式编程的本质
函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如y=x*x函数计算x的平方根,只要x的平方,不论什么时候调用,调用几次,值都是不变的。
纯函数式编程语言中的变量也不是命令式编程语言中的变量,即存储状态的单元,而是代数中的变量,即一个值的名称。变量的值是不可变的(immutable),也就是说不允许像命令式编程语言中那样多次给一个变量赋值。比如说在命令式编程语言我们写“x = x + 1”,这依赖可变状态的事实,拿给程序员看说是对的,但拿给数学家看,却被认为这个等式为假。
函数式语言的如条件语句,循环语句也不是命令式编程语言中的控制语句,而是函数的语法糖,比如在Scala语言中,if else不是语句而是三元运算符,是有返回值的。
严格意义上的函数式编程意味着不使用可变的变量,赋值,循环和其他命令式控制结构进行编程。
函数式编程关心数据的映射,命令式编程关心解决问题的步骤,这也是为什么“函数式编程”叫做“函数式编程”。
二 实例
1 number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8]
2 count = 0
3 sum = 0
4
5 for i in range(len(number)):
6 if number[i]>0:
7 count += 1
8 sum += number[i]
9
10 print sum,count
11
12 if count>0:
13 average = sum/count
14
15 print average
首先循环列表中的值,累计次数,并对大于0的数进行累加,最后求取平均值。
这就是命令式编程——你要做什么事情,你得把达到目的的步骤详细的描述出来,然后交给机器去运行。
这也正是命令式编程的理论模型——图灵机的特点。一条写满数据的纸带,一条根据纸带内容运动的机器,机器每动一步都需要纸带上写着如何达到。
那么,不用这种方式如何做到呢?
number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8]
positive = filter(lambda x: x>0, number)
positive = list(positive)
average = reduce(lambda x,y: x+y, positive)/len(positive)
print average
这段代码最终达到的目的同样是求取正数平均值,但是它得到结果的方式和 之前有着本质的差别:通过描述一个列表->正数平均值 的映射,而不是描述“从列表得到正数平均值应该怎样做”来达到目的。
再比如,求阶乘
通过Reduce函数加lambda表达式式实现阶乘是如何简单:
1 from functools import reduce
2 print (reduce(lambda x,y: (x*y), range(1,6))) # ((((1, 2), 3), 4), 5)
又比如,map()函数加上lambda表达式(匿名函数)可以实现更强大的功能:
1 squares = map(lambda x : x*x ,range(9))
2 print (squares)# <map object at 0x10115f7f0>迭代器
3 print (list(squares))#[0, 1, 4, 9, 16, 25, 36, 49, 64]
三 函数式编程有什么好处呢?
1)代码简洁,易懂。
2)无副作用
由于命令式编程语言也可以通过类似函数指针的方式来实现高阶函数,函数式的最主要的好处主要是不可变性带来的。没有可变的状态,函数就是引用透明(Referential transparency)的和没有副作用(No Side Effect)。
生活不会突变,你要做的只是耐心和积累。人这一辈子没法做太多的事情,所以每一件都要做得精彩绝伦。你的时间有限,做喜欢的事情会令人愉悦,所以跟随自己的本心。