函数


函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码。


定义函数

python支持自定义函数,即由我们自己定义一个实现某个功能的函数。

自定义函数规则:

①函数代码块以def关键字开头,后接函数标识符名称和圆括号"()"。

②所有传入的参数和自定义变量都必须放在圆括号中,可以在圆括号中定义参数。

③函数的第一行语句可以选择性的使用文档字符串,用于存放函数说明。

④函数内容以冒号开始,并且要缩进。

⑤return[表达式]结束函数,选择性的返回一个值给调用方。不带表达式的return相当于返回None


python 定义函数使用def关键字,一般格式如下;

def 函数名 (参数列表):

     函数体

或者更直观的表示为:

def(arg1,arg2,...argN):

函数的名字必须是以字母开头,可以包括下划线"_"。和定义变量,不能把python的关键字定义成函数的名字。函数内的语句数量是任意的,每个语句至少有一个空格的缩进,以表示该语句属于这个函数。函数体必须保持缩进一致,因为在函数中,缩进结束就表示函数结束。

例如:

def hello():
    print('hello,world')
#调用函数
hello()

C:\python\python.exe C:/python.py/hanshu.py

hello,world

上例中的hello()就是自定义的函数,在函定义完成后做了函数自我调用。如果不调用就没有任何输出。

需要注意以下几点:

(1)没有return语句时,函数执行完毕也会返回结果,不过结果为None。

(2)return None 可以简写为return。

(3)在python中定义函数时,需要保持函数体中同一层级的代码缩进一致。

在一个函数中可以输出多条语句,并能做相应的运算操作,以及输出运算结果。

例如:

def printmore():
    print('该函数可以输出多条语句,我是第一条。')
    print('我是第二条')
    print('我是第三条')
#调用函数
printmore()

C:\python\python.exe C:/python.py/hanshu.py

该函数可以输出多条语句,我是第一条。

我是第二条

我是第三条

定义输出数字和计算的函数并执行。

例如:

def  mixoperation():
    a=10
    b=20
    print(a)
    print(b)
    print(a+b)
    print('a+b 的和等于:',a+b)
    #调用函数
mixoperation()

C:\python\python.exe C:/python.py/hanshu.py

10

30

a+b的和等于: 30


定义空函数可以使用pass语句。

例如:

def donothing():
    pass
donothing()

C:\python\python.exe C:/python.py/hanshu.py


执行结果为没有任何输出。

函数的目的是把一些复杂操作隐藏起来,用于简化程序结构,使程序更容易阅读。函数在调用前必须先定义。


函数的参数

调用函数时可以使用一下参数类型:

(1)必须是函数。

(2)关键字是参数。

(3)默认是参数。

(4)可变参数。

(5)组合参数。


必须参数

必须参数必须以正确的顺序传入函数。调用时数量必须和声明时一样。

例如:

def paramone(str):
    print('the param is',str)
    print('我是一个传入参数,我的值是:',str)
paramone('hello,world')

C:\python\python.exe C:/python.py/hanshu.py

the param is hello,world

我是一个传入参数,我的值是: hello,world


上例定义了一个必须传入一个参数的函数paramone(str),传入的参数为str,结果是将'hello,world'传给str。

注意:paramone()函数,不传入参数或传入一个以上参数,都会报错。所以对此类函数,必须传递对应正确个数的参数。


关键字参数

关键字参数和函数调用关系紧密,函数调用使用关键字参数确定传入的参数值。

使用关键字参数允许调用函数时参数的顺序与声明时不一致,因为python解释器能够用参数名匹配参数值。

def personinfo(age,name):
    print('年龄:',age)
    print('名称:',name)
    return
print('-----------按参数顺序传入参数--------------')
personinfo(18,'徐伟')
print('-----------按参数顺序传入参数,指定参数名--------------')
personinfo(name='徐伟',age=18)
print('-----------参数顺序传入参数,指定参数名--------------')
personinfo(age=18,name='徐伟')

C:\python\python.exe C:/python.py/hanshu.py

-----------按参数顺序传入参数--------------

年龄: 18

名称: 徐伟

-----------按参数顺序传入参数,指定参数名--------------

年龄: 18

名称: 徐伟

-----------参数顺序传入参数,指定参数名--------------

年龄: 18

名称: 徐伟

由上例可以看出对于关键字参数personinfo()函数,只要指定参数名,输入参数的顺序对结果就没有影响,都能得到正确的结果。


默认参数

调用函数时,如果没有传递参数,就会使用默认参数。

使用默认参数,就是在定义函数时,给参数一个默认值。如果没有给调用函数的参数赋值,调用的函数就会使用这个默认值。

例如:

def  defaultparam(name,age=23):
     print('hi,我叫:',name)
     print('我今年:',age)
     return
defaultparam('徐伟')

C:\python\python.exe C:/python.py/hanshu.py

hi,我叫: 徐伟

我今年: 23

从上例可以看出,在函数调用时没有对age赋值,在输入结果中使用了函数定义时的默认值。

重新调用上面的函数

def defaultparam(name, age=23):
    print('hi,我叫:', name)
    print('我今年:', age)
    return
defaultparam('徐伟',18)

C:\python\python.exe C:/python.py/hanshu.py

hi,我叫: 徐伟

我今年: 18

由上例函数可以看出,执行结果是我们传入的参数。由此得知:对默认参数传值时,函数执行时调用的是我们传入的值。

注意:默认参数一定要放在非默认参数的后面。

设置多个默认参数

例如:

def defaultparam(name,age=23,addr='内蒙古'):
    print('hi,我叫:', name)
    print('我今年:', age)
    print('我现在在:',addr)
    return
print('--------------传入必须参数------------------')
defaultparam('杜宇恒')
print('------传入必须参数,更改第一个默认默认数值----')
defaultparam('杜宇恒',18)
print('------传入必须参数,默认参数值都更改----------')
defaultparam('杜宇恒',18,'北京')
print('----传入必须参数,指定默认参数名并更改参数值---')
defaultparam('杜宇恒',addr='北京')
print('------传入必须参数,指定参数名并更改值---------')
defaultparam('杜宇恒',addr='北京',age=23)
print('-----第一个默认参数不带参数名,第二个带--------')
defaultparam('杜宇恒',18,addr='北京')
print('--------------两个默认参数都带参数名----------')
defaultparam('杜宇恒',age=23,addr='北京')
print('---第一个默认参数带参数名,第二个不带,会报错---')
defaultparam('杜宇恒',age=23,'北京')

C:\python\python.exe C:/python.py/hanshu.py

--------------传入必须参数------------------

hi,我叫: 杜宇恒

我今年: 23

我现在在: 内蒙古

------传入必须参数,更改第一个默认默认数值----

hi,我叫: 杜宇恒

我今年: 18

我现在在: 内蒙古

------传入必须参数,默认参数值都更改----------

hi,我叫: 杜宇恒

我今年: 18

我现在在: 北京

----传入必须参数,指定默认参数名并更改参数值---

hi,我叫: 杜宇恒

我今年: 23

我现在在: 北京

------传入必须参数,指定参数名并更改值---------

hi,我叫: 杜宇恒

我今年: 23

我现在在: 北京

-----第一个默认参数不带参数名,第二个带--------

hi,我叫: 杜宇恒

我今年: 18

我现在在: 北京

--------------两个默认参数都带参数名----------

hi,我叫: 杜宇恒

我今年: 23

我现在在: 北京

---第一个默认参数带参数名,第二个不带,会报错---

  File "C:/python.py/hanshu.py", line 27

    defaultparam('杜宇恒',age=23,'北京')

                                   ^

SyntaxError: positional argument follows keyword argument

从以上执行结果可以总结出:

(1)无论有多少默认参数,默认参数都不能放在必须参数之前。

(2)无论有多少默认参数,若不传入默认参数值,则使用默认值。

(3)若要更改某一个默认参数值,又不想传入其他参数,且该默认参数的位置不是第一个,则可以通过参数名更改想要更改的默认参数值。

(4)若有一个默认参数通过传入参数名更改参数值,则其他想要更改的默认参数都需要传入参数名,否则会报错。

(5)更改默认参数值时,传入参数的顺序不需要根据定义的函数中的默认参数的顺序传入,不过最好时同时传入参数名,否则容易出现执行结果与预期不一致的情况。


可变的参数

如果需要一个函数能够处理的参数声明时更多,这些参数叫做可变参数。

可变参数基本语法如下:

def functionname([formal_arges,]*var_arges_tuple):

    "函数_文档字符串"

function_sutite

return[expression]

加了星号(*)的变量名会存放所有未命名的变量参数。如果变量参数在函数调用时没有指定参数,就是一个空元组。我们也可以不向可变函数传递未命名的变量。

例如:

def  personinfo(arg,*vartuple):
    print(arg)
    for var in vartuple:
         print('我属于不定长参数部分:',var)
    return
print('------------不带可变参数-------------')
personinfo('杜宇恒')
print('------------带两可变参数-------------')
personinfo('杜宇恒',21,'北京')
print('------------带5个可变参数-------------')
personinfo('杜宇恒',21,'北京',123,'内蒙古','happy')

C:\python\python.exe C:/python.py/hanshu.py

------------不带可变参数-------------

杜宇恒

------------带两可变参数-------------

杜宇恒

我属于不定长参数部分: 21

我属于不定长参数部分: 北京

------------带5个可变参数-------------

杜宇恒

我属于不定长参数部分: 21

我属于不定长参数部分: 北京

我属于不定长参数部分: 123

我属于不定长参数部分: 内蒙古

我属于不定长参数部分: happy

可变参数的好处,我在参数前面加了一个星号,在函数内部,参数前面的星号将所有值放在同一个元组中,通过这种方式将这些值收集起来,然后使用。参数vartuple接收的是一个元组,调用函数时可以传入任意个数的参数,也可以不传。

还可以使用两个"*",即使用"**"处理关键字的参数。

例如:

other = {'城市':'北京','爱好':'pyhon'}
def persionfo(name,number,**kw):
    print('名称:',name,'学号:',number,'其他:',kw)
persionfo('杜宇恒',666,城市=other['城市'],爱好=other['爱好'])

C:\python\python.exe C:/python.py/hanshu.py

名称: 杜宇恒 学号: 666 其他: {'城市': '北京', '爱好': 'pyhon'}

函数调用时可以使用更简单的方式调用.

例如:

other = {'城市':'北京','爱好':'pyhon'}
def persionfo(name,number,**kw):
    print('名称:',name,'学号:',number,'其他:',kw)
persionfo('杜宇恒',666,**other)

C:\python\python.exe C:/python.py/hanshu.py

名称: 杜宇恒 学号: 666 其他: {'城市': '北京', '爱好': 'pyhon'}

执行结果和上面的一样,写法法上面却简单了不少.此处**other表示把other这个字典的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个字典,注意kw获得的字典时other复制的,对kw的改动不会影响函数外的other.


组合参数

在python中定义函数可以使用必须参数、关键字参数、默认参数和可变关键字参数,这4种参数可以组合使用.

注意定义参数的顺序必须是必须参数、默认参数、可变长参数和关键字参数.

def exp(p1, p2, df=0, *vart, **kw):
    print('p1=', p1, 'p2=', p2, 'df=',df, 'vart=', vart, 'kw=', kw)
exp(1, 2)
exp(1, 2, c=3)
exp(1, 2, 3, 'a', 'b')
exp(1, 2, 3, 'abc', x=9)

C:\python\python.exe C:/python.py/hanshu.py

p1= 1 p2= 2 df= 0 vart= () kw= {}

p1= 1 p2= 2 df= 0 vart= () kw= {'c': 3}

p1= 1 p2= 2 df= 3 vart= ('a', 'b') kw= {}

p1= 1 p2= 2 df= 3 vart= ('abc',) kw= {'x': 9}


由上例可以看到,使用了组合参数,在调用函数时,python解释器会自动按照参数位置和参数名把队形的参数传进去.

还可以使用tuple和dict调用函数:

例如:

def  exp(p1,p2,df=0,*vart,**kw):
       print('p1=',p1,'p2=',p2,'df=',df,'vart=',vart,'kw=',kw)
args = (1,2,3,4)
kw = {'x':8,'y':'9'}
exp(*args,**kw)

C:\python\python.exe C:/python.py/hanshu.py

p1= 1 p2= 2 df= 3 vart= (4,) kw= {'y': '9', 'x': 8}

由上例可以看到,任意函数都可以通过类似func(*arges,**kw)的的形式调用,无论参数是如何定义的.


执行流程

(1)为了保证函数的定义先于首次调用执行,我们需要知道执行语句的顺序,即执行流程.

(2)执行总是从程序的第一行代码开始,从上到下、从左到右,按顺序依次执行第一条语句.

(3)函数定义并不会改变程序的执行流程,不过函数代码块中的语句并不是立即执行,而是等函数被程序调用时才执行.

(4)函数调用可以看作程序执行流程中的一个迂回路径,遇到函数调用时,并不会直接继续执行下一条语句,而是跳到函数体的第一行,继续执行完函数代码块中的所有语句,在跳回原来离开的地方.

(5)函数代码块中可以调用其他函数,当程序流程运行到一个函数时,可能需要执行其他函数中的语句.但当执行这个函数的语句时,又可能需要调用执行另一个函数的语句.

(6)pyhon对于程序运行到哪里有很好的记录,所以在每个函数结束后,程序都能跳回它离开的地方,直到执行到整个程序的结尾才会结束.

(7)python代码不一定要一行一行按照书写顺序阅读,有时按照执行的流程阅读可以更好理解代码的含义.


形参和实参

python函数的两种类型参数,一种是函数定义里的形参,一种时调用函数时传入的实参.

在函数内部会将实参的赋值给形参

例如:

def personinfo(age,name):
    print('年龄:',age)
    print('名称:',name)
    return

在该函数中,函数名personinfo后面的参数列表age和name就是实参,在函数体中分别将age和name的值传递给age和name,函数体中age和name就是形参.

注意:在函数体内部都是对形参进行操作,不能操作实参,即对实参做出更改.

内置函数组合规则在自定义函数上同样适用.

例如:(对上例自定义的personinfo函数可以使用任何表达式作为实参)

def personinfo(age,name):
    print('年龄:',age)
    print('名称:',name)
    return
personinfo(21,'杜宇恒'*2)

C:\python\python.exe C:/python.py/hanshu.py

年龄: 21

名称: 杜宇恒杜宇恒

由执行结果可以看到,可以使用字符串的乘法表达式作为实参.

在python中,作为实参的表达式会在函数调用前执行.在上例中,实际先执行'杜宇恒'*2的操作,将执行的结果作为一个实参传递到函数体中.

注意:作为实参传入函数的变量名称和函数定义里形参的名字没有关系.函数只关心形参的值,而不关心它在调用前叫什么名字.


变量的作用域

简单来说,作用域就是一个变量的命名空间.在python中,程序的变量并不是在任何位置都可以访问,访问权限决定于这个变量是在哪里赋值的,代码中变量被赋值的位置决定哪些范围的对象可以访问这个变量,这个范围就是命名空间.

变量的作用域决定哪一部分程序可以访问特定的变量名称.python有两种最基本的变量作用域:局部变量和全局变量.


局部变量

在函数内定义的变量名只能被函数内部引用,不能在函数外引用,这个变量的作用域是局部的,也称为局部变量.

定义的变量如果是在函数体中第一次出现,就是局部变量.

例如:

def  func():
x = 100
print(x)

在func函数中,x是在函数体中被定义的,并且也是第一次出现所以x是局部变量.

局部变量只能在函数体中被访问,超出函数体的范围就会报错

例如:

def  func():
    x = 100
    print('变量x:%s'% x)
print('函数体外访问变量x:%s'%x)
func()

C:\python\python.exe C:/python.py/hanshu.py

Traceback (most recent call last):

  File "C:/python.py/hanshu.py", line 10, in

    print('函数体外访问变量x:%s'%x)

NameError: name 'x' is not defined

报错:第10行的x没有定义;原因是第10行语句没有在函数体中.

如果把x作为实参传入函数体中,在函数体中不定义变量x,会将x认为是怎样的变量呢?

例如:

def  func(x):
    print('局部变量x为:',x)
func(10)

C:\python\python.exe C:/python.py/hanshu.py

局部变量x为: 10

从上例输出结果可以看出,输出了局部变量的值.这是应为参数的工作原理类似于局部变量,一旦进入函数体,就成为了局部变量.

如果在函数外定义了变量x并赋值,在函数体中能否使用x呢?

例如:

x = 50
def func():
    print('x等于',x)
func()

C:\python\python.exe C:/python.py/hanshu.py

x等于 50

由上例输出结果可以看出,在函数体中可以直接使用函数体外的变量,这种变量称之为全局变量.


如果在函数外定义了变量x并赋值,将x作为函数的实参,在函数体中更改x的值,函数体外x的值是否跟着变更呢?

例如:

x = 50
def func(x):
    print('x等于',x)
    x=2
    print('局部变量x变为',x)
func(x)
print('x 一直是',x)

C:\python\python.exe C:/python.py/hanshu.py

x等于 50

局部变量x变为 2

x 一直是 50

有输出结果可以看到,在函数体中更改变量的值并不会更改函数外的值.这是应为调用func函数时创建了新的命名空间,它作用于func函数代码块.赋值语句x=2只是在函数体的作用域起作用,不能影响外部作用中的x.可以看到调用x时,它的值并没有改变.


全局变量

在函数外,一段代码最开始赋值的变量可以被多个函数引用,这就是全局变量.全局变量可以在整个程序范围内访问.

例如:

total =0 #这是一个全局变量
def sum(arg1,arg2):
    total = arg1+arg2; #在这里这一个局部变量
    print('函数内部是局部变量:',total)
    return  total
def totalprint():
    print('total的值是',total)
    return   total
print('函数求和结果:',sum(10,20))
totalprint()
print('函数外是全局变量:',total)

C:\python\python.exe C:/python.py/hanshu.py

函数内部是局部变量: 30

函数求和结果: 30

total的值是 0

函数外是全局变量: 0

由上例结果可以看出,全局变量可在全局使用,在函数体中更改全局变量的值不会影响全局变量在其他函数或语句中使用.

小例子:

num = 100
def func():
    num=200
    print('函数体中的num的值为:',num)
func()
print('函数外的num的值为:',num)

C:\python\python.exe C:/python.py/hanshu.py

函数体中的num的值为: 200

函数外的num的值为: 100

我们定义了一个名为num的全局变量,在函数体中也能定义一个名为num的全局变量,在函数体中使用的时函数体中的num变量,在函数体外使用num变量时使用的是全局变量.

注意:函数中使用某个变量时,如果该变量名既有全局变量又有局部变量,就默认使用局部变量.

注意:要将全局变量变为局部变量,只需在函数体中定义一个和全局变量名称一样的变量即可.

局部变量变为全局变量

例如:

num = 100
print('函数调用前的num的值为:',num)
def func():
    global  num
    num = 200
    print('函数体中num的值为:',num)
func()
print('函数调用结束后num的值为:',num)

C:\python\python.exe C:/python.py/hanshu.py

函数调用前的num的值为: 100

函数体中num的值为: 200

函数调用结束后num的值为: 200

上例可以看出,在函数体中变量num前加了一个global关键字后,函数调用结束后,在函数外使用num变量时,值变为和函数体中的一样的值了.

由此得知:要在函数中将某个变量定义为全局变量,在需要被定义的前面加一个关键字global即可.

在函数体中定义global变量后,在函数体中对变量做的其他操作也是全局性的.

例如:

num = 100
print('函数调用前的num的值为:',num)
def func():
    global  num
    num = 200
    num +=100
    print('函数体中num的值为:',num)
func()
print('函数调用结束后num的值为:',num)

C:\python\python.exe C:/python.py/hanshu.py

函数调用前的num的值为: 100

函数体中num的值为: 300

函数调用结束后num的值为: 300

由上例可以看出,在函数体中对定义的全局变量num做了一次加100的操作,num的值由原来的200变为300,在函数体外获得的num的值也变为300了.


有返回值和无返回值函数

若定义函数时没有使用return语句,则默认返回一个None.要返回一个None,可以只写一个return,但要返回具体的数值,就需要在return后面加上需要返回的内容.

对于函数的定义来说,使用return语句可以向外提供该函数执行的一些结果;对于函数调用者来说,是否可以使用函数中执行的一些操作结果,就在于函数是否使用return语句返回了对应的执行结果.

在python中,有的函数会产生结果(如数学函数),我们称这种函数为返回值函数(fruitfulfunction);有的函数执行一些动作之后不返回任何值,我们称这种函数为无返回值函数.

刚当我们调用有返回值函数时,可以使用返回的结果做相关操作;当我们使用无返回值或返回None的函数时,只能得到一个None值.

例如:

def noreturn():
    print('noreturn函数不写return语句')
def justreturn():
    print('justreturn函数只写return,不返回具体内容')
    return 
def returnval():
    x=10
    y=20
    z=x+y
    print('returnval函数写return语句,并返回求和结果.')
    return  z
print('函数noreturn调用的结果:',noreturn())
print('函数 justreturn调用结果:',justreturn())
print('函数retrrnval调用结果:',returnval())

C:\python\python.exe C:/python.py/hanshu.py

noreturn函数不写return语句

函数noreturn调用的结果: None

justreturn函数只写return,不返回具体内容

函数 justreturn调用结果: None

returnval函数写return语句,并返回求和结果.

函数retrrnval调用结果: 30

由上例可以看出,定义函数时不写return或只写一个return语句返回的都是None.如果写了返回具体内容,调用函数时就可以获取具体内容.


函数的好处

(1)新建一个函数,让我们有机会为一组语句命名,成为一个代码块,这样更有利于阅读代码块,并且组织后的代码更容易调试.

(2)函数方法可以减少重复代码的使用,让程序代码总行数更少,之后修改代码时只需要少量修改就可以了

(3)将一个很长的代码片段拆分成几个函数后,可以对每一个函数进行单独调试,单个函数调试通过后,再将它们组合起来形成一个完整的产品.

(4)一个设计良好的函数可以在很多程序中复用,不需要重复编写.


返回函数

函数中可以返回函数

例如:

def calc_sum(*args):
    ax = 0
    for n in args:
          ax = ax + n
    return  ax
print(calc_sum(1,2,3))

C:\python\python.exe C:/python.py/hanshu.py

6

上例定义了一个可变的求和函数,该函数允许传入多个参数,最后返回求得的和.

如果不想立刻求和,而是在后面的代码中根据需要再计算,该怎么办?

例如:

def sum_late(*args):
    def calc_sum():
         ax=0
         for n in args:
              ax = ax + n
         return  ax
    return  calc_sum #对于此处定义的函数,这里没有返回求和的结果,而是返回了一个求个的函数
print('调用sum_late的结果:',sum_late(1,2,3,4))
calc_sum=sum_late(1,2,3,4)
print('调用的calc_sum的结果:',calc_sum())

C:\python\python.exe C:/python.py/hanshu.py

调用sum_late的结果: .calc_sum at 0x0000000000B4CE18>

调用的calc_sum的结果: 10

由上例结果可以看到,调用定义函数时美誉直接返回求和结果,而是返回了一串字符(这个字符其实就是函数).当执行返回函数时,才真正计算求和的结果.

注意:上例中,在函数sum_late中又定义了函数calc_sum,并且内部函数calc_sum可以引用外部函数sum_late的参数和局部变量.当sum_late返回函数calc_sum时,相关参数和变量都保存在返回的函数中,称为闭包(Closure).这种结构威力极大.

还需要注意:当调用函数sun_late()函数时,每次调用都会返回一个新的函数,即使传入相同参数也是如此.

闭包的定义:如果在一个内部函数里对外部函数(不是在全局作用域)的变量进行引用,内部函数就称作为闭包.

例如:

def count():
    fs=[]
    for i in range(1,4):
           def f():
                 return  i*i
           fs.append(f)
    return fs
f1,f2,f3 = count()
print('f1的结果是:',f1())
print('f2的结果是:',f2())
print('f3的结果是:',f3())

C:\python\python.exe C:/python.py/hanshu.py

f1的结果是: 9

f2的结果是: 9

f3的结果是: 9

从输出结果可以看到三个函数返回的都是9,这是因为返回函数时引用了变量I,但它并非立刻执行.等到3个函数都返回时,它们所引用的变量i已经成了3,因此最终结果为9.

注意:返回闭包时,返回函数不要引用任何循环变量或后续会发生的变量,否则很容易出问题.

如果必须要引用循环变量怎么办? 

如下:

def count():
    def f(j):
        def g():
            return  j*j
        return g
    fs = []
    for  i in range(1,4):
        fs.append(f(i))  #f(i)立刻执行,因此i的当前值被传入f()
    return  fs
f1,f2,f3 = count()
print('f1的结果是:',f1())
print('f2的结果是:',f2())
print('f3的结果是:',f3())

C:\python\python.exe C:/python.py/hanshu.py

f1的结果是: 1

f2的结果是: 4

f3的结果是: 9


递归函数

如果一个函数在内部调用自身,这个函数就称为递归函数.

递归函数的简单定义如下:

      def recurision():

       return recursion()

这类递归函数被称作无穷递归(infinite recursion),理论上是永远不会结束.每次调用函数都会用掉一点内存,在足够多的函数调用发生后,内存空间被占满,程序就会报错.

有用的递归函数需要满足以下条件:

(1)当函数直接返回值时有基本实例(最小可能性问题).

(2)递归实例,包括一个或多个问题最小部分的递归调用.

使用递归关键在于将问题分解为小部分,递归不能永远继续下去,因为它总是以最小可能性问题结束,而这些问题又存储在基本实例中.

实现函数自身调用:函数每次调用时都会创建一个命名空间,也就是当函数调用"自身"时,实际上运行的是两个不同的函数(简单理解为:一个函数具有两个不同的命名空间)

递归示例,基数按阶乘n!=1X2X3X4...Xn,用函数fact(n)表示可以看出:

fact(n)= n! =1X2X3...X(n-1)Xn=(n-1)! X n=fact(n-1)Xn

所以,fact(n)可以表示为nXfact(n-1),只有n=1时需要特殊处理.

fact(n)用递归方式定义函数如下:

def fatc(n):
    if n==1:
          return 1
    return  n * fatc(n-1)
print('调用递归函数执行结果为:',fatc(5))

C:\python\python.exe C:/python.py/hanshu.py

调用递归函数执行结果为: 120

由输出结果可以看出,函数已正确输出5的阶乘结果.

递归函数的优点:定义简单,逻辑清晰.

注意:使用递归函数需要注意防止栈溢出.在计算机中,函数调用时通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会增加一层栈帧;每当函数返回,栈就会减一层栈帧.由于栈的大小不是无限的,因此递归调用的次数过多会导致栈溢出.

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环效果一样,把循环看成一种特殊的尾递归函数也可以.

尾递归是指在函数返回时调用函数本身,并且return语句不能包含表达式,这样编译器或解释器就可以对尾递归进行优化,使尾递归本身调用多少次都只能占用一个栈帧,从而避免栈溢出的情况.

尾递归优化

如下:

def fact(n):
     return  fact_iter(n,1)
def fact_iter(num,product):
    if num == 1:
        return  product
    return  fact_iter(num -1,num * product)
print('调用递归函数执行结果为:',fact(5))

C:\python\python.exe C:/python.py/hanshu.py

调用递归函数执行结果为: 120

可以看到return  fact_iter(num -1,num * product)仅返回递归函数本身,num-1和num* product 在函数调用前就会被计算,不影响函数调用.

调用尾递归时如果做了优化,栈就不会增长,因此无论调用多少次都不会溢出.


匿名函数

匿名函数就时不再使用def语句这样的标准形式定义一个函数.

python使用lambda创建匿名函数.

lambda只是一个表达式,函数体比def简单很多.

lambda的主体是一个表达式,而不是一个代码块,仅能在lambda表达式中封装有限的逻辑.lambda函数拥有自己的命名空间,不能访问自有参数列表之外或全局命名空间的参数.

lambda函数的语法只包含一个语句,如下:

             lambda[arg1[,arg2,.....argn]]:exprssion

案例比较:

例如:求一个列表中大于3的元素.

方法一:通过程式编程实现,也是常规的方法,如下:

L1=[1,2,3,4,5]
L2=[]
for i in L1:
        if i >3:
             L2.append(i)
print('列表中大于3的元素有:',L2)

C:\python\python.exe C:/python.py/hanshu.py

列表中大于3的元素有: [4, 5]

方法二:通过函数式编程实现,运用一个filter,给出一个判断条件,如下:

def func(x):
    return  x>3
f_list=filter(func,[1,2,3,4,5])
print('列表中大于3的元素有:',[item for item in f_list])

C:\python\python.exe C:/python.py/hanshu.py

列表中大于3的元素有: [4, 5]

方法三:使用匿名函数,如下: 

print('列表中大于3的元素有:',[item for item in filter(lambda x:x>3,[1,2,3,4,5])])

C:\python\python.exe C:/python.py/hanshu.py

列表中大于3的元素有: [4, 5]

从方法三可以看出,lambda一般应用于函数式编程,代码简洁,常和filter等函数结合使用.

对方法三使用lambda的示例进行解析:

x 为 lambda 函数的一个参数.

: 为分割符

x>3 则是返回值,在lambda函数中不能有retrrn,其冒号(:)后面就是返回值.

item for item in filter 是python3中filter函数的取值方式,应为从python3起,filter函数返回的对象从列表改为迭代器(filter object).filter object支持迭代操作,比如for循环:

 

 

匿名函数的应用场景:

 (1)如果程序一次性使用\不需要定义函数名时,用匿名函数可以节省内存中的变量定义空间.

 (2)如果想让程序更加简洁,使用匿名函数就可以做到

 匿名函数的三个规则

 (1)一般有一行表达式,必须有返回值

 (2)不能有return.

 (3)可以没有参数,也可以有一个或多个参数.

 示例:

 无参匿名函数:

t = lambda : True
print(t())

C:\python\python.exe C:/python.py/hanshu.py

True

带参数匿名函数:

lambda x: x**3 # 一个参数
lambda x,y,z:x+y+z #多个参数
lambda x,y=3:x*y#允许参数存在默认值

匿名函数调用:

c = lambda  x,y,z:x*y*z
print(c(2,3,4))
c=lambda  x,y=2:x+y#使用了默认值
print(c(10))#如果不输入,就使用默认值2

C:\python\python.exe C:/python.py/hanshu.py

24

12


偏函数

偏函数是将索要承载的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数的后续参数,除非使用关键字参数.

示例:实现一个取余函数,取得整数100对不同数m的100%m的余数,如下:

from functools import  partial
def mod(n,m):
    return  n%m
mod_by_100 = partial(mod,100)
print('自定义函数,100对7取余结果为:',mod(100,7))
print('调用偏函数,100对7取余结果为:',mod_by_100(7))

C:\python\python.exe C:/python.py/hanshu.py

自定义函数,100对7取余结果为: 2

调用偏函数,100对7取余结果为: 2

偏函数可以降低函数函数调用的难度