概念
函数是一段具有特定功能,可重复使用的代码,python提供了很多内置函数,如:print(),input(),len()函数等,以及标准库函数,math库中的sqrt()函数等,除此之外用户还可以自己定义函数,称为自定义函数。
函数的定义
def 函数名 ([形式参数列表]):
函数体
[return 返回值]
其中def是Python的关键字,用于定义函数
形式参数列表(简称形参)是调用该函数时传递给函数的参数,可以是零个或多个,当传递的多个参数时各参数之间用逗号隔开,[]表示可选的内容
函数体是每次调用函数执行的代码,由一行或多行语句组成。
定义函数时须注意:
当函数参数为零个时,也必须保留一对空的圆括号
圆括号后面的冒号不可省略
函数体对于def关键字必须保持一定的空格缩进
函数的调用
函数定义后,须调用函数才能执行函数体,实现特定功能。
函数名 ([实际参数列表])
实际参数列表(简称实参)表示要传递给函数的具体值
当我们定义好函数后执行,会发现什么都没有,这是就是因为没有调用函数的原因,定义函数可以理解为只是找好了员工还没有工作
调用函数时,会将函数内的语句一并执行,不要再加print输出
函数的传参
传入参数的功能是:在函数进行计算的时候,接受外部(调用时)提供的数据
定义函数时,圆括号内的参数为形参,表名函数声明要使用的参数,多个参数用逗号隔开
调用函数时,圆括号内的参数为实参,表示函数执行时真正使用的参数值,多个参数用逗号隔开
在Python中,函数参数的数据类型可分为不可变数据类型(如整数,浮点数,字符串,元组等)和可变数据类型(列表,字典,集合等)当参数为不可变数据类型时,在函数内部直接修改不会影响实参
但当参数的数据类型为可变数据类型时,在函数内使用下标或其他方式增删改元素,实参也会更改
参数类型
位置参数
位置参数,有时也称必备参数,调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。
可以看到这里因为第三个调用函数时传递的实参数量与形参不符,触发了类型错误:
add()缺少一个必需的位置参数:‘y’
同样,多参数也会触发报错:add()需要两个位置参数,但是提供了三个
默认值参数
在定义函数时,可以为函数的参数设置默认值,此参数成为默认值参数,在调用带默认值的函数时,可以不用为默认值参数传值,此时函数将会直接使用定义时的默认值,也可以通过显式赋值来替换掉默认值
可以看到设定默认值之后即使传入实参与形参数量不符也不会报错
还可以使用函数名.__defaults__查看函数当前设定的所有默认值,返回值的类型是一个元组,其中的元素依次表示当前设定的每个默认值
如果形参的默认值是一个变量,那么形参的默认值只取决于函数定义时该变量的值(也就是指定默认值变量在函数定义之前最接近函数的值)如果函数后或者函数中重新给变量赋值不会更改默认值参数
关键字参数
关键字参数是指调用函数时按形参名传递值。使用关键字参数允许函数调用时参数的顺序与定义时的不一样,python解释器能够使用参数名匹配参数值
不定长参数
通常在定义一个参数时,若希望函数能够处理比定义时更多的参数,此时可在函数中使用‘*’或‘**’符号标识不定长参数
*:表示接受任意多个参数,并将其存放在一个元组中
**:表示接受任意多个参数,并将其存放在字典中,实参格式类似关键字参数
调用greet函数,传入多个值,这些值会自左往右依次匹配形参,首先将 ‘hi!’传递给say,接着将‘小兰,小五,小红’组成一个元组传递给names,最后将“小李 = ‘你好’,小明 = ‘hello!’”转换为字典传递给name_say
函数的返回值
函数除了可以在函数体中直接输出数据,还可以返回一个或多个值,称为函数的返回值return
return用于退出函数并将程序结果返回到函数调用位置继续执行,同时返回零个或多个值
拥有return语句就要使用print函数打印返回值,如果没有return语句,默认将以return None结束,即返回空值
None作为一个特殊的字面量,用于表示空。无意义,其有非常多的应用场景
用在函数无返回值上
用在if判断
在if判断中None等同于False
一般用于在函数中主动返回None,配合if判断做相关处理
因为None本身就代表着false,所以进行判断就可以得到我们想要的结果
才试了一下,原来直接if re也可以表示如果re为真,只是跟if not的意思反了过来
用于声明无内容的变量上
定义变量,但暂时不需要变量有具体值,可以使用None来代替
可以看到打印出来的也是None,a的数据类型是NoneType
返回值也可以有多个,元素之间用逗号隔开。函数调用后多个返回值将以元组的形式保存
函数的嵌套
函数的嵌套定义就是在函数内又定义了另一个函数
函数的嵌套调用就是在函数中调用另外一个函数
例:
在函数内嵌套定义函数,在函数内嵌套调用函数
需要注意的是,这里的fun_b函数只在丁一开始到fun_a函数结束范围内有效
在外部嵌套调用函数,调用fun_b先输出2,再调用fun_a打印1,最后输出3
函数的递归
函数的递归是一种嵌套调用的特殊形式,即函数直接或间接的调用其本身,套娃
直接递归调用就是在函数总调用函数本身
可以看到在fun函数中调用了fun函数,导致一直输出1,直到重复太多次被编译器报错警告
间接递归调用时在fun函数中调用fun1函数,而fun1函数又调用了fun函数
但如果只能这样也就是个无线死循环,没有实际意义,因此所有递归函数都需要设定终止条件
利用递归求阶乘
数学上阶乘通常定义为:
n! = n(n-1)(n-2)...(1)
虽然之后几行,但是讲起来有点小复杂
首先先输入要求阶乘的整数,我这里是5,然后调用fact函数,进入到函数体判断是否为0,如果为0就返回1(也充当着结束条件的作用)
因为传入的参数是5所以进入else(这里定义一个y接受数据,主要是方便在调试中查看数据变化),执行n * fact(n - 1),因为这里又调用了fact函数并且传入了参数n-1,所以有重新进入到fact函数中,直到最后n = 1然后传入 n-1等于0时函数return 1,结束递归调用
结束以后,y的值就要一层一层的进行运算了,首先是因为结束时fact函数的返回值为1,所以传递到fact(n-1)就等于1,本来n也等于1,所以就是1 * 1,y = 1,暂用f(1)代替,那么继续执行return将f(1)的值传递给上层f(2),这时n等于2 ,所以f(2)就等于2 * f(1) = 2 * 1 = 2,依次类推
f(3):n = 3,f(3) = 3 * f(2) = 3 * 2 = 6
f(4):n = 4,f(4) = 4 * f(3) = 4 * 6 = 24
f(5):n = 5,f(5) = 5 * f(4) = 5 * 24 = 120
最后得出的结果打印出来就是5的阶乘,文字可能有点难理解,这里画了一个草图,更加直观
斐波那契数列
利用函数的递归计算斐波那契数列的第n项,斐波那契数列从第三项开始,元素值为前两个元素的和,如:1,1,2,3,5,8,13,21,34,55
首先定义的当n = 1和 n = 2时都返回1,也就是给函数递归调用的终止条件,当n等于5时,根据课本也画了一个草图,很直观就能知道斐波那契数列的执行步骤,递归终止后,就会利用返回值向上进行计算,得到最终值
变量的作用域
当一个程序中包含多个函数时,可在各函数中分别定义变量,所以变量的作用域指的是变量的作用范围( 变量在哪里可用,在哪里不可用)
主要分两类:局部变量和去全局变量
局部变量
局部变量是定义在函数体内部的变量,只能在变量定义开始到函数结束的范围内使用,它与函数外具有相同名称的变量没有任何关系,此外函数的形参也属于局部变量,作用范围仅限于函数内部
可以看到当我们调用函数的时候可以直接输出num变量的值
但是当我们在函数外再次打印num的值是就会触发报错,说num没有被定义,这就是局部变量,当num变量执行到函数结束时就被销毁了
全局变量
所谓全局变量,指的是在函数体内、外都能生效的变量
如果有一个数据,在函数A和函数中都要使用,该怎么办?
只需要将这个数据设定为全局变量就好了
这就是在函数外定义的全局变量,对变量定义之下的整个程序都生效,两个函数都没有定义num变量的值,可以直接访问全局变量获取
global关键字
global关键字可以在函数内部声明变量为全局变量
使用global关键字可以分为以下两种情况
变量已在函数外创建,如果需要在函数内使用或修改该变量,需要在函数内使用关键字global声明该全局变量
变量没有在函数外创建,在函数内直接使用global关键字声明全局变量,然后赋值,调用该函数后,会创建新的全局变量,在函数外也可以使用或修改该变量
可以看到当我们不使用global关键字时,test_a中的num = 250,相当于test_a又在函数内声明了一个局部变量,跟外部的全局变量num没有任何关系,不会改变函数外num的值
当我们使用了global关键字声明num就会发现,global关键字会将在函数内定义的变量成为全局变量,当调用test_a函数时,函数外定义的变量也会发生改变
nonlocal关键字
如果要在一个嵌套函数中使用或修改嵌套外部作用域中的变量,须使用nonlocal关键字修饰该变量
可以看到当我们在内层函数inner中修改变量num并不会更改外层函数outer的值,只会在函数内部生效
一开始在外层outer函数中num = 1 当我们想要更改变量时,只需要在内层函数inner内部使用nonlocal函数声明num可以实现对外层函数变量的改变
nonlocal关键字与global关键字的区别
这两个关键字拿出来我一开始也是蒙的,不知道有啥区别
为了实践,我又定义了一个iner函数跟inner函数并列,应该可以说是outer函数的子函数
当我们在inner函数中利用nonlocal关键字声明的时候,发现iner函数并没有享受到nonlocal关键字的变量声明,由此可得不管iner函数属于inner还是与其并列,nonlocal关键字作用于外部内嵌函数的变量,不会对声明变量以下的函数生效
以下两张截图都是在最下层的iner函数中利用nonlocal关键字声明变量,可以看到在其上的函数,如果没有在内部给变量num赋值时,也会享受到nonlocal关键字的变量声明
如果产生了对同意变量的赋值,也就是相当于在函数中又定义了一个局部变量,所以不管iner函数是属于inner函数还是与其并列,都不会改变inner函数中定义的num变量值
还有就是,如果在函数内定义的变量再使用nonlocal关键字修改,不会产生新的全局变量,当我们在函数外打印变量时会报错说变量num没有被定义
global关键字虽然不能够作用于嵌套函数,但是global关键字可以改变全局变量,同时可以定义新的全局变量;nonlocal关键字只能改变外层函数变量,不能定义新的外层函数变量,并且nonlocal也不能改变全局变量。