本节内容
1. 函数基本语法及特性
2. 参数与局部变量
3.递归
4.函数式编程介绍
5.高阶函数
1.函数基本语法及特性
三种编程范式:
1、面向过程:过程——> def
2、面向对象:类——> class
3、函数式编程:函数——> def
函数是什么?
函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的,编程中的函数在英文中也有很多不同的叫法。在C中只有function,在Java里面叫做method。
定义: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可
使用函数的好处:
- 减少重复代码
- 使程序变的可扩展
- 使程序变得易维护
语法定义
1
2
3
4
5
|
def test(x): #函数定义方法、函数名、变量 '''The function difinition!''' #对该函数的描述 x+=1 #函数体 return x #返回值
test(1) #调用函数 |
函数:
def func1():
'''testing1'''
print('in the func1')
return 0
过程:
def func2():
'''testing2'''
print('in the func2')
由上得出,过程就是没有返回值的函数。
可以带参数
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#下面这段代码 a,b = 5 , 8 c = a * * b print (c) #改成用函数写 def calc(x,y): res = x * * y return res #返回函数执行结果 c = calc(a,b) #结果赋值给c变量 print (c) |
return的作用
1、终止函数的运行,即return后面的代码都将不会被执行
2、return给调用该函数的位置,返回一个值。如,x=test1(),x就会接收到return的返回值
3、后续程序有时需要根据之前函数return返回值的不同,进行不同的判断和运算。
return的返回值
return 1, 'hello', ['alex','wupeiqi'], {‘name’:'alex'}
所以,return的个数没规定,类型没规定。把return后面的内容放入元组中全部返回,本质上return还是返回了一个值。如果之前有一个函数def test():, 用return test 时,返回值是test函数的内存地址。
2.函数参数与局部变量
调用方法:
1、test()执行,()表示调用函数test,()可以有参数也可以没有
参数
1、形参和实参
形参:形式参数,不是实际存在的,是虚拟变量,在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参(实参个数、类型应与形参一一对应)
实参:实际参数,调用函数时传给函数的参数,可以是常量,变量,表达式,函数,传给形参
区别:形参是虚拟的,不占用内存空间,形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传送单向,实参传送给形参,不能形参传给实参。
2、关键字调用,与形参顺序无关;位置调用与形参一一对应。
【注意】:关键字参数不能写在位置参数之前
3、默认参数
def test(x,y=2):
print(x,y)
test(1)
上述代码在定义函数时对形参赋值的过程叫做设置默认参数,被赋的值叫做对应形参的默认参数。
默认参数特点:调用函数时,默认参数非必须传递
4、参数组:
1)def test(*args):
print(args)
test(1,2,3,4,5) #或者等价于后面这种赋值方式: test(*[1,2,3,4,5])
输出:(1,2,3,4,5)
这时候会把5个位置实参全部传给形参args,并且以元组的形式保存下来。定义位置调用参数组时,以*开头即可,args的名字可以是任意的。但编写规范就是*args。
2)def test2(**kwargs):
print(kwargs)
test2(name='gavin',age=25,sex='M') #或者等价于后面这种赋值方式: test2(**{‘name’:'gavin','age':25,'sex':'M'})
输出:{‘name’:'gavin','age':25,'sex':'M'}
这时候会把3个关键字调用的实参全部传递给形参kwargs,并且以字典的方式保存,关键字为key,给关键字赋的值为value。定义关键字调用参数组时,以**开头,kwargs的名字可以是任意的,但是编写规范就是**kwargs。
3)小结:
*args把N个位置参数,转换成元组的方式
**kwargs把N个关键字参数,转换成字典的方式
【注意】: 参数组一定要放在形参的最后面
位置参数一定要写在关键字参数之前。所以对应的,形参部分先写*args,再写**kwargs。
局部变量
1
2
3
4
5
6
7
8
9
10
|
name = "Alex Li" def change_name(name): print ( "before change:" ,name) name = "金角大王,一个有Tesla的男人" print ( "after change" , name) change_name(name) print ( "在外面看看name改了么?" ,name) |
输出
1
2
3
|
before change: Alex Li after change 金角大王,一个有Tesla的男人 在外面看看name改了么? Alex Li |
全局与局部变量
在函数内修改全局变量
name=‘gavin
def change_name(name):
global name
name='Gavin Simons'
pass
chang_name(name)
在上述这种情况下,在函数内部使用global指令,声明了name为全局变量,此时在函数内部再修改name时,修改的是全局变量。同时如果在函数内部声明全局变量之前,即在文件开头并没有相同名字的全局变量,则函数内的全局变量会被新创建,在函数外部也可以进行调用。但是,虽然这样可行,但是永远不要这么干(包括在函数内修改全局变量和在函数内创建全局变量),因为这么做很难去调试,不知道是那个函数创建或修改了全局变量。
局部变量和全局变量的特殊情况
names=['Gavin','Simons','Jack','Rain']
def chang_name():
names[0] = ‘詹姆斯西蒙斯’
print('inside func',names)
change_name()
print(names)
输出:inside func ['詹姆斯西蒙斯','Simons','Jack','Rain']
['詹姆斯西蒙斯','Simons','Jack','Rain']
所以只有像简单的数据类型(比如,字符串和整数)是不能在函数中修改的。但是复杂的数据类型(比如,列表、字典、集合、类,元组在什么时候都不能改)这些都是在局部里面可以直接改全局的。因为存放方式和简单数据类型不同,后者存放的是地址,而不是具体的值。
3. 递归
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def calc(n): print (n) if int (n / 2 ) = = 0 : return n return calc( int (n / 2 )) calc( 10 ) 输出: 10 5 2 1 |
递归特性:
1. 必须有一个明确的结束条件
2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
堆栈扫盲http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html
递归函数实际应用案例,二分查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
data = [ 1 , 3 , 6 , 7 , 9 , 12 , 14 , 16 , 17 , 18 , 20 , 21 , 22 , 23 , 30 , 32 , 33 , 35 ] def binary_search(dataset,find_num): print (dataset) if len (dataset) > 1 : mid = int ( len (dataset) / 2 ) if dataset[mid] = = find_num: #find it print ( "找到数字" ,dataset[mid]) elif dataset[mid] > find_num : # 找的数在mid左面 print ( " 33[31;1m找的数在mid[%s]左面 33[0m" % dataset[mid]) return binary_search(dataset[ 0 :mid], find_num) else : # 找的数在mid右面 print ( " 33[32;1m找的数在mid[%s]右面 33[0m" % dataset[mid]) return binary_search(dataset[mid + 1 :],find_num) else : if dataset[ 0 ] = = find_num: #find it print ( "找到数字啦" ,dataset[ 0 ]) else : print ( "没的分了,要找的数字[%s]不在列表里" % find_num) binary_search(data, 66 ) |
4.函数式编程介绍
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
一、定义
简单说,"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。
主要思想是把运算过程尽量写成一系列嵌套的函数调用。举例来说,现在有这样一个数学表达式:
(1 + 2) * 3 - 4
传统的过程式编程,可能这样写:
var a = 1 + 2;
var b = a * 3;
var c = b - 4;
函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:
var result = subtract(multiply(add(1,2), 3), 4);
因此,函数式编程的代码更容易理解。
要想学好函数式编程,不要玩py,玩Erlang,Haskell。
5.高阶函数
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
即把函数本身当作一个参数,传给另一个函数。
1
2
3
4
5
6
|
def add(x,y,f): #f为一个函数形参 return f(x) + f(y) res = add( 3 , - 6 , abs )#abs是函数实参,其实就是函数abs的内存地址 print (res) |