本文实例讲述了python定义函数功能与用法。分享给大家供大家参考,具体如下:
1.函数的意义
一般数学上的函数是,一个或者几个自变量,通过某种计算方式,得出一个因变量。
y = f(x)
在python中,为了使操作更加简洁,就引入了函数这个概念。
python中的函数,可以把一大串要反复使用的代码“定义”(封装)成一个函数,给予这个函数一个标识符作为函数名,设置自变量和因变量。然后要使用这一大串代码的时候,就调用这个我们自己创造的函数,输入自变量,然后会返回给我们因变量。
2.函数的定义
在python中,对应数学函数的自变量和因变量的值,叫做参数和返回值。
用def来定义一个函数,下面是一个通用的模型,函数都这么定义:
1
2
3
4
5
|
def 函数名(参数列表):
...
代码块
...
return 返回值
|
然后举一个例子,假设我们要判定一个学生的成绩是及格还是不及格:
1
2
3
4
5
6
7
8
9
|
def judge_score(score):
judge = none
if score > = 60 :
judge = true
print ( 'pass' )
else : #虽然成绩应该在0-100之间,但是我懒,就不限定范围了。
judge = false
print ( 'fail' )
return judge
|
然后试一下:
1
2
3
4
5
6
|
judge_score( 59 )
fail
j = judge_score( 61 )
pass
print (j)
true #说明j接收到的值为true
|
这段代码很简单,但是也不能每次要判断一次成绩,就敲出这么一大串,所以为了便捷,就给这段代码用def(define,定义)封装成一个函数。
给一个函数名judge_score,后面调用就可以用judge_score()
。
score是我们要输入的自变量,也就是要在这个judge_score()
中进行运算的值。
接下来要注意:
这个'pass'和'fail' 不是函数的返回值!
这个'pass'和'fail' 不是函数的返回值!
这个'pass'和'fail' 不是函数的返回值!
‘pass'和'fail' 是函数内部的print()打印出来的内容,函数的返回值是写在return后面的内容。
return后面可以做运算,也可以直接写变量。如果这个函数的语句块内没有写return语句,那么说明没有定义返回值,也就是说调用函数什么都没有返回,如果拿一个标识符来接受这个函数的返回值,只能接受到none。
return后面的返回值可以是多个,多个的话,就用,隔开,然后封装成一个元组再返回。
函数名也是标识符,有的时候可以用callable()来判断这个标识符是不是一个可以调用的。
1
|
callable (judge_score) #返回值为true
|
注意别再检查的函数名标识符后面加()。
3.函数的参数
在定义函数时,函数名后面的()里面叫做参数列表,这个参数列表里的参数,都是要在下面的代码块中要使用的。
3.1 形式参数和实际参数
形式参数,就是指在定义函数时,参数列表里写出的参数,他们都还只是标识符,都会在下面的代码中出现。此时此刻他们没有具体的值,只是一个“外壳”,所以叫形式参数。
比如y = 3x 里面这个x,只是一个形式,没有具体的值。
实际参数,是指在调用参数时,函数名后面的括号内给出具体的值,每个值都会与某一个形式参数对应。这些值是存在的,不止有个外壳。也可以算作是数学函数中的自变量,用它们来计算出因变量。比
如y = 3x ,假设x=3,这个3就是实际参数。
注意,每一个形式参数都必须获得一个值(如果没有默认值),才能进行计算,否则会报错!
3.2 传递参数的类型和参数的类型
3.2.1 传递参数类型
传递参数,我的理解就是把实际参数和形式参数连接起来。
定义一个函数bmi,用来计算人的body mass index:
1
2
3
|
def bmi(height, weight):
index = "%.2f" % (weight / (height * * 2 )) #留两位小数
return index
|
位置传参
调用它:
1
|
bmi( 1.71 , 65 ) #返回值为22.23
|
可以看到我们的形式参数有height和weight,实际参数是1.71和65。在调用函数时,这两个实际参数没有指定谁是height,谁是weight,是按定义函数时,参数列表里面的形式参数的位置一一对应起来。这样就叫做位置传参。
关键词传参
调用它:
1
|
bmi(weight = 65 , height = 1.71 ) #返回值22.23
|
如果函数的调用者知道形式参数的标识符,也可以直接用 形式参数名=值 这样的方式来传递参数,可以理解为赋值。因为是用关键字传递参数,所以位置可以和定义函数时参数列表内的形式参数顺序不同。
混合传参
有的时候,我们可能有一些参数要用位置传参,有一些要用关键字传参。万幸的是他们可以混合使用。但是要遵守一定的规则:
1
|
bmi( 1.71 , weight = 65 ) #返回值为22.23
|
前面的1.71用的是位置传参,后面65用的是关键词传参。
注意,关键字传参一定要写在位置传参之后!不然就会报错。
3.2.2 参数类型
位置参数的可变参数
比如有这样一个需求,要输入若干个数字,然后求出这若干个数字中的最大值和最小值。分析一下,若干个数字,也就是不知道数字的个数,这样也就不知道设置多少个形式参数。这样就可以用,*args,可变参数。
如果在定义函数时*args的左侧有参数,那么在调用时,实际参数依次给予*arg左边的用位置传参的普通形式参数之后,剩下的实际参数无论多少都会被*args接收,和切片有一定的相似之处,但是差别也不小。
举个例子
1
2
3
4
5
6
7
8
9
10
|
def maxmin(x, * nums): #这个x是为了显示*args的收取范围
length = len (nums)
for i in range ( 1 ,length): #这是选择排序的思路,走一趟,练练手
maxindex = 0
minindex = 0
if nums[i] > nums[maxindex]:
maxindex = i
if nums[i] < nums[minindex]:
minindex = i
print ( 'x={};{};max:{};min:{}' . format (x,nums,nums[maxindex],nums[minindex]))
|
调用这个函数:
1
2
|
maxmin( 100 , 12 , 23 , 34 , 45 , 67 , 9 )
x = 100 ;( 12 , 23 , 34 , 45 , 67 , 9 ); max : 12 ; min : 9
|
可以看出,在给出的实际参数中,除了第一个100,被x接收了,剩下的实际参数都被*nums接收了,然后封装成了一个元组(讲道理,用逗号隔开了又被一个标识符接收,确实应该封装成元组)。
注意,和切片不同的是,定义函数时*args会无限接收实际参数,不会给后面的形式参数留值,所以在定义函数时的参数列表里,位置参数的可变参数一定要在普通位置参数之后!
关键字参数的可变参数
关键字参数的可变参数,不是收集多个用关键字传参的实际参数,而是收集关键字传参的关键字和值,并把他们当做一个键值对,收集在一个字典内,在代码块中使用。
1
2
3
4
|
def test(x = 1 , * * nums):
print (x,nums)
test(c = 3 ,x = 2 ,a = 1 ,b = 2 )
2 { 'c' : 3 , 'a' : 1 , 'b' : 2 }
|
可以看出,用关键字传参,可以不按位置顺序来,先把
关键字参数的可变参数,和位置参数的可变参数一样,会无限接收实际参数,不过是接收实际参数的关键字和值,组成键值对。
3.3 默认参数
有的时候,一些参数变化不频繁,以上面定义的这个bmi函数来看,假设一个班里面的99%的同学,身高变化很大,体重都是75kg,每次用这个函数算同学的bmi,都要重新输入一遍身高体重,比较麻烦,所以可以在定义函数时,给形式参数设置一个默认值:
1
2
3
|
def bmi(height = 1.80 , weight = 75 ):
index = "%.2f" % (weight / (height * * 2 )) #留两位小数
return index
|
注意,这是在定义函数时,就设置好默认值,然后调用:
1
2
3
4
5
6
|
bmi( 1.80 ) #此时按height=1.80,weight=75来计算
bmi( 1.71 ) #此时按height=1.71,weight=75来计算
bmi( 65 ) #此时按height=65,weight=75来计算,注意和上面的区别
bmi( 1.71 , 65 ) #此时按height=1.71,weight=65来计算
bmi(height = 1.90 ) #此时按height=1.90,weight=75来计算
bmi( 1.80 , weight = 80 ) #此时按height=1.80,weight=80来计算
|
也就是说,如果没有传参,那么形式参数就会按定义参数时设置的默认值来计算。
如果用位置传参,就按形式参数的位置顺序,依次往后覆盖,如果实际参数没给够,那么没有接收到位置传参的形式参数就会用默认值。(也就是说,在定义函数时,虽然设置了默认值,看起来和关键字参数似的,但是它也是有前后位置顺序的。)
如果用关键字传参,理解就比较简单了,如果给了,就用关键字传参给的实际参数,如果没给,就用默认值。
总之,如果普通的形式参数没有设置默认值,就必须要接收一个实际参数来使用。如果形式参数在定义时设置了默认值,调用函数时,没有给出实际参数,就用默认值,如果给了一个实际参数,就用这个新的实际参数把默认值覆盖。
3.4 keyword-only参数
这个参数可以理解为,必须用关键字传参才能获得的参数。
这种参数为了区别于普通的形式参数,位置有所改变,在定义参数时,放在*args(位置参数的可变参数)和**kwargs(关键字参数的可变参数)之间,就代表这个参数是keyword-only参数。
1
2
|
def test( * words, x):
print (words, x)
|
调用这个函数:
1
2
3
4
5
6
|
test( 1 , 2 , 3 , 4 )
#这样会报错,因为这四个都是位置传参,都被*word截获了,x没有值
test( 1 , 2 , 3 , x = 4 ) #这个没毛病
( 1 , 2 , 3 ) 4
test(x = 4 ) #这个也没毛病,*word一个值都有截获
() 4
|
注意,在定义函数时,参数列表里,keyword-only参数不能放在关键字参数的可变参数之后,因为keyword-only参数要用关键字传参,**kwargs要截获所有的关键字传参的实际参数,keyword-only参数永远也收不到值。
keyword-only参数还有另一种定义形式,比如,我们想要这个函数都用关键字传参,并且不想接收任何位置传参的实际参数(就是一用位置传参就报错),可以用一下这种形式:
1
2
|
def test( * , x, y):
print (x,y)
|
调用它:
1
2
3
|
test( 1 , x = 100 , y = 99 ) #会报错,因为1没有形参来接收它
test(x = 100 , y = 99 ) #没毛病
100 99
|
3.6 参数列表的顺序***
重中之重:
定义函数时:(位置参数,带缺省值的位置参数,位置参数的可变参数,keyword-only参数,关键字参数的可变参数)
调用函数时:(用位置传参的实际参数,用关键字传参的实际参数)
但是一定要确保,定义函数时,参数列表里的每一个参数(可变参数除外,因为可变参数可以收集到0个实际参数)都要有值可以使用。
例:
1
2
3
4
5
|
def test(a,b,c,d = 5 , * nums,x = 'x' ,y = 'y' , * * ddict):
print (a,b,c,d)
print (nums)
print (x,y)
print (ddict)
|
调用它:
1
2
3
4
5
|
test( 97 , 98 , 99 , 100 , 101 , 102 ,x = 'xx' ,y = 'yy' ,name = 'tom' ,age = 10 )
97 98 99 100
( 101 , 102 )
xx yy
{ 'name' : 'tom' , 'age' : 10 }
|
可以看到,有六个用位置传参的实际参数,虽然d有默认值,但是按位置来看,d也会接收到一个新的值100,因此nums只截获了两个位置参数。
然后是keyword-only参数,x和y,都得用关键字传参。如果x和y不指定的话,就会使用默认值x='x',y='y'。
然后是关键字参数的可变参数,不能说是截获,得说keyword-only参数只给它剩下了两个,然后他们组成了一个字典
还要注意,在传参的时候,位置传参要放在最前面。然后是关键字传参,从关键字传参中把keyword-only参数挑走,然后剩下的给**kwargs。
3.5 实际参数的解构
有时候,定义参数的时候,有很多形参。但是实际参数,都存在一个list或者说别的数据结构中,一个个拿出来很麻烦,所以可以种参数解构来完成:
1
2
3
4
5
6
7
8
9
10
|
def maxmin(x, * nums):
length = len (nums)
for i in range ( 1 ,length):
maxindex = 0
minindex = 0
if nums[i] > nums[maxindex]:
maxindex = i
if nums[i] < nums[minindex]:
minindex = i
print ( 'x={};{};max:{};min:{}' . format (x,nums,nums[maxindex],nums[minindex]))
|
这个函数,是找若干个数中的最大值还有最小值的,它可以接受若干个值(因为定义函数时用的是*args)。但是如果给我们的是一个列表,那还真把每个元素都提取出来,输入一遍吗,太蠢了,可以直接在调用函数的时候解构。
用*解构:
1
2
3
|
lst = [ 100 , 12 , 23 , 34 , 45 , 67 , 9 ]
maxmin( * lst)
100 9
|
再看一个例子,用**解构:
1
2
3
4
5
6
|
def test( * nums):
print (nums)
test({ 'a' : 1 , 'b' : 2 , 'c' : 3 })
({ 'a' : 1 , 'b' : 2 , 'c' : 3 },) #打印出来一个元组,说明传入的字典没有解构
test( * { 'a' : 1 , 'b' : 2 , 'c' : 3 })
( 'a' , 'b' , 'c' ) #打印的是key,字典解构是key的集合,没毛病
|
1
2
3
4
|
def test(a,b,c): #因为要接字典解构出来的
print (a,b,c)
test( * * { 'a' : 1 , 'b' : 2 , 'c' : 3 }) #**这样是把字典里的键值对解构出来
1 2 3
|
4. 函数的返回值
在一些已经知道函数中,有的有返回值,比如input()
、sorted()
等,它们都可以用一个标识符来接收,形成一个变量。有的没有返回值,比如print()
、list.append()
等方法,都是没有返回值的。
在自定义函数中,也能做到这一点,用的是return语句:
1
2
3
4
5
6
7
|
def test(x = 5 ):
a = x * * 2
print (a)
b = test()
25 #这个25是print(a)的效果
b
none
|
这说明,我们定义的函数test()
,它是没有返回值的,虽然出现了结果,但那时print()
语句的打印输出,用标识符来接收test()
的输出,什么都接收不到(什么都没有就是none)。
如果用return语句:
1
2
3
4
5
6
7
8
|
def test(x = 5 ):
a = x * * 2
print (a)
return a
b = test()
25 #这个还是print(a)的打印输出
b
25 #这个是b刚刚接收函数的返回值
|
也就是说return语句后面的值,可以作为这个函数的返回值,这个值可以是一个变量,也可以是一个表达式,还可以写多个用逗号隔开的值,不过最后会被封装成一个元组返回。
1
2
3
4
|
def test():
....
return 1 , 2 , 3 , 4 #返回值其实是(1, 2, 3, 4)
a, b, c, d = test() #这里用解构来接收返回值,会比较方便
|
如果写了return语句,return后面的值会作为返回值输出,但是如果不写return语句,或者只写了一个return,后面没有值,就说明这个函数没有返回值(其实是隐式调用了return = none)
自定义函数中,可以有多条return语句,在定义时候不会报错,但是这些return语句只有一条会被执行,执行完这个return语句,函数就结束,其不会理会其他的return语句。
1
2
3
4
5
6
7
8
|
def comparethree(n):
if n > 3 :
return 3
else :
print ( 'less than three' )
return false
print ( 'nothing' )
return 'nothing'
|
看着个自定义函数,如果实参大于3,返回值就是3,如果小于等于3,返回值就是false。
因为他用的是分支语句,只会进入其中一个分支,所以只有一个return会被执行。这个分支结构完成后,还有一个print()和return,这两个是不会被执行的,因为分支语句中都有return,怎么着都会执行其中的一个,执行完就结束这个函数,所以后面的'nothing'和返回值字符串'nothing'都不会执行。
所以还能看出,return语句不一定是定义函数时写的最后一条语句,但最后一条执行的代码,一定是return(这么说是因为,如果没有写return,会隐式调用return = none,它默认写在定义函数时的最后一句)。
5. 函数的作用域
5.1 作用域的概念
在自定义函数时,就涉及到一个问题,在函数内部定义的变量,在函数之外能不能使用。这个问题要分类讨论。
先介绍一个定义,作用域,一个标识符的可见范围,就是这个标识符的作用域,也叫做变量的作用域(个人觉得叫变量的作用范围比较好理解)。
1
2
3
4
5
6
7
8
9
10
|
a = 5
def test():
b = 10
print (a, b)
return b
test( 10 )
5 10
print (a)
5
print (b) #这里会报错
|
这里a的作用域是整个程序,也可以说在整个程序的运行环境中都可见。因为定义a是在最外部,所以函数的内部,不管多少层嵌套函数,都可以使用a。
这种a叫做全局变量,它的作用域是全局,全局作用域就是在整个程序的运行环境中都可见。
再看b,在函数内部定义的变量,在全局中是不可见的,它只能在这个函数的内部使用,所以最后在全局中print(b)的话会报错,并且异常是nameerror,说这个b没有被定义过。
实际参数的作用域也是只在函数的内部,因为形参是在函数的内部,给形参传参之后,它还是在函数的内部。
这种b就叫做局部变量,它只作用于当前的函数或者类,局部变量的作用范围不能超过当前的局部作用域。
再看一个例子:
1
2
3
4
5
6
|
x = 1
def t():
x = 100
print (x)
t()
print (x)
|
x在全局下定义过,但是函数内部对这个x又重新定义了。但是在全局中,x仍然是原来的值,这说明x=100这个标量是局部变量,作用域只在这个函数内部,对外部不可见。
5.2 函数嵌套和作用域
有的时候,定义一个函数,内部可能还要再定义一个函数:
1
2
3
4
5
6
7
8
9
10
|
a = 5
def test():
b = a + 5
print (b)
def test2():
c = b + 5
print (c)
return c
d = test2()
return a, b, d
|
分析代码可以按这种顺序,碰到def定义函数,跳过,等出现了调用,再回头看这个函数的定义。
看这个函数,有双层嵌套,调用它:
1
|
test() #注意test2()在全局中是调用不出来的
|
全局中有个a = 5 ,它的作用域是全局,所以在函数内部也可以用它,得到b = 10,打印一下,然后看见def test2(),跳过,然后下面就是调用test2(),再返回来看test2()的定义,b的作用域是局部的,包括当前这层函数和内层函数,所以得到c = 15,作为return返回,用标识符d接收,最后test()这一层的return返回a, b, d。
执行结果如下:
1
2
3
4
|
test()
10
15
( 5 , 10 , 15 ) #这个是return的值,是output
|
test()这层的函数定义时,为啥不return c呢,是因为c是test2()这一层函数中定义的变量,是局部变量,对于外部是不可见的,如果test()中使用了c,且没有定义过,就会报错。
这样会多多少少出现一些麻烦,所以根据需要,python给出了两种声明global和nonlocal。
global,它能把一个变量变成全局变量。
1
2
3
4
5
6
7
8
9
10
11
|
a = 10
def t():
global x
global a
x = 1
a = 100
return x
print (x) #x是在函数内部定义的,但是在全局的环境下也可以使用
1
print (a) #在内部重新定义的a也会改变
100
|
但是定义函数,其中有一个目的就是封装,和外部环境隔绝,通过传参来计算,但是这样用global定义为全局变量,有悖于这个理念,所以尽量别用global。
nonlocal,它能把一个变量变成局部变量,就是这个变量的可见范围是最外层的函数,但是全局中不可见。
这里还要引入两个概念,
*变量,就是没有在本地作用域中定义的变量
闭包,指在嵌套函数中,内层函数用到了外层函数定义的变量(注意是外层函数,不是全局中定义的)这一种现象。
1
2
3
4
5
6
7
8
9
10
|
a = 1
def t():
a = 10
print (a)
def t1():
nonlocal a
a = 100
print (a)
t1()
print (a)
|
调用t():
1
2
3
4
5
6
|
print (a)
t()
1 #这是在全局环境中的a,值没变是a
10 #这是最外层函数的a
100 #这是声明a是nonlocal之后,给a改变值后print的a
100 #这是在最外层函数中print的a,已经发生了改变,和内层函数的a同步
|
5.3 函数默认值参数的作用域
参数可以作文局部变量理解,当一个有默认值的形参,没有接收到实参时,就会调用默认值:
1
2
3
4
5
6
7
8
9
10
|
def t(a = [ 1 , 2 , 3 , 4 ]):
a.append( 5 )
print (a)
t() #连续调用3次
t()
t()
[ 1 , 2 , 3 , 4 , 5 ]
[ 1 , 2 , 3 , 4 , 5 , 5 ]
[ 1 , 2 , 3 , 4 , 5 , 5 , 5 ]
|
三次都使用的是形参的默认值,但是print的结果不一样,那么就是说明默认值改变了。
这是因为,形参的默认值都会保存下来,可以查看,用__defaults__:
1
2
|
t().__defaults__
([ 1 , 2 , 3 , 4 , 5 , 5 , 5 ],) #这是三次执行之后的默认值
|
可以看到,默认值是保存在一个元组中,如果元组中的元素是引用类型(比如list),那么储存的是一个引用地址,后面通过引用地址对这个默认值修改的话,修改的是他们共用的数据。默认值通过这个引用地址去找数据时,找到的是改过的数据。
有时候我们需要这种改变,但是有时候不需要,所以有两种解决办法:
1
2
3
4
|
def t(a = [ 1 , 2 , 3 , 4 ]):
a = a[:] #影子拷贝,拷贝一个a,修改就是在这个新的a中
a.append( 5 )
print (a)
|
但是这个有弊端,如果默认值内部还有引用类型,影子拷贝并不能拷贝引用类型。
还有一种方法:
1
2
3
4
5
|
def t(a = none):
if a is none:
a = [ 1 , 2 , 3 , 4 ]
a.append( 5 )
print (a)
|
如果不指定a,那么a就用默认值none,就给a重新赋值一个[1,2,3,4],这时候引用地址已经换了,不是默认值的引用地址,但是这个新的引用地址就是我们想要的a的默认值,然后进行操作。
下次如果还是不指定a的话,a还是none,还会重新赋值成[1,2,3,4]。相当于虽然想要的a默认值是引用类型,但是对a操作不改变我们想要的a的默认值。
6. legb原则
在定义函数时出现了函数嵌套,对于每个变量的作用域就要了解清楚。
这个变量被用到时,就要在这个解释器中搜寻这个变量是在哪一层赋值的,找个搜寻顺序就是l local e enclosing g global b build-in,也就是说,
先在本地(这一层作用域)查找,
没找到就在这个函数的外部函数中查找,
如果还是没找到,就在全局中查找,
如果仍旧没找到,就在build-in库中查找。
比如一个典型的错误:
1
2
3
4
|
x = 5
def t():
print (x)
x + = 5
|
函数内print要用到x,首先要在local中查找,查到了x,但是注意,此时的x是作用域为函数内部,不能使用外部的x=5,这是给x定义,先计算右边,右边的x依旧没有被定义,所以以一个x它自己(没有值)来定义它,就会报错。
1
2
3
|
x = 5
def t():
print (x)
|
如果这样的话,函数内部local没有,嵌套函数enclosing也没有,那么就去global全局中找,找到了x=5,使用它。
至于build-in 是指我们用到的一些内建函数,print(),input()等,都是定义在build-in库中的,要使用时,也是一层一层往上找,显然l e g中都没有,然后去b中找,找到了,使用它。
7. 销毁函数
两种方法:
第一个是重新定义同样函数名的函数。
第二个是用del:
1
|
del t
|
这样就把上面定义的t给删除了,和删除变量一样,只是切断了标识符和内存中函数的联系,删除了这个标识符,使这个函数在内存中的引用计数减一,不是真的删除内存中的函数。
希望本文所述对大家python程序设计有所帮助。
原文链接:https://blog.csdn.net/LittleHuang950620/article/details/81939996