第四章 函数总结
1、函数返回值return总结:
- Python函数使用return语句返回 ” 返回值 “,函数不能同时返回多个值,有且仅有一个值返回;
- 所有函数都有返回值,如果没有return语句,隐式调用return None;
- return 语句并不一定是函数的语句块的最后一条语句,但如果return语句调用后return语句后的所有部分将不再执行;
- 函数可以存在多个return语句,但是只有一条可以执行。如果没有return则隐式调用return None;
- 如果有必要,可以显示调用return None,可以简写为 return;
- 如果函数执行了return语句,函数就会返回,当前被执行的return语句之后的其他语句就不会被执行了;
- return作用:结束函数调用、返回值;
2、(函数)作用域:一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域;
- 全局作用域:在整个程序运行环境中都可见;
- 局部作用域:(1)在函数、类等内部可见;
(2)局部变量使用范围不能超过其所在的局部作用域;
- 外层变量作用域在内层作用域可见;(但是内层作用域中将外层变量赋值就不可以,因为赋值及重新定义,也就是在内层作用域中重新定义了一个变量。跟外层循环没关系。)
- 内层作用域中,如果重新定义(赋值及重新定义)了外层中的变量标识符,相当于当前作用域中重新定义了一个新的变量、而且并没有覆盖外层作用域中的变量;
eg:
x = 5
def foo():
x += 1
foo()
将会报UnboundLocalError
- x = 5是一个全局变量,函数中 x += 1 其实是 x = x + 1
- 相当于在foo函数内部定义一个局部变量 x ,那么foo内部所有x都是这个局部变量x了;
- 此时这个 x 还没有完成赋值,就被右边拿来做加1操作了;
3、作用域关键字
- 全局变量 global;
(1)使用 global 关键字的变量,将foo内的 x变量 声明为外部的全局作用域中定义的 x;
(2)全局作用域中必须有 x 的定义,否则就需要在函数内自己赋值定义变量(注意该变量有关键字global);
(3)但是如果全局作用域中没有变量 x 的话,在函数内部赋值及定义,在内部作用域为一个外部作用域的变量 x 赋值,不是在内部作用域定义个新变量,所以 x += 1不会报错。注意:这里的 x 的作用域还是全局的;
global总结:
- x += 1 这种是特殊形式产生的错误的原因?
答:先引用后赋值,而Python动态语言是赋值才算定义,才能被引用。解决办法,在这条语句前增加 x = 0 之类的赋值语句,或者使用 global 告诉内部作用域,去全局作用域查找变量定义;
- 内部作用域使用 x = 5 之类的赋值语句会重新定义局部作用域使用的变量 x ,但是,一旦这个作用域中使用global声明x为全局的,那么x = 5 相当于在为全局作用域的变量x赋值;
- global 使用原则
- 外部作用域变量会被内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离;
- 如果函数需要使用外部全局变量,请使用函数的形参传参解决;
- 一句话:不用global。
2. nonlocal 关键字
- 使用了nonlocal关键字,表示该局部变量中的变量使用的是上级作用域中的变量;注意该上级不能是外部的全局变量;
3、默认值的作用域
- 因为函数也是对象,python把函数的默认值放在了属性中,这个属性就伴随着这个函数对象的整个生命周期;
- 查看 函数名 . __defaults__ 属性;
- 函数的默认值是使用元组来保存的;
- 属性 __defaults__ 中使用元组保存所有位置参数默认值;
- 属性 __kwdefaults__ 中使用字典保存所有keyword—only参数的默认值;
注意:使用可变类型作为默认值,就可能修改这个默认值;
默认值的修改
- 使用影子拷贝创建一个新的对象,永远不能改变传入的参数;
- 【1】通过值得判断就可以灵活得选择创建或者修改传入对象;
【2】这种方式灵活,应用广泛;
【3】很多函数得定义,都可以看到使用None这个不可变得值作为默认参数,可以说这是一种惯用法;
4、闭包
- 自由变量:未在本地作用域中定义的变量。例如定义在函数外的外层函数的作用域中的变量;(在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。)
- 闭包:指的是内层函数引用到了外层函数的自由变量,就形成了闭包。
注意:如果变量使用的是global关键字,但这是使用的是全局变量,而不是闭包;如果要对普通变量的闭包,可以使用nonlocal;
5、函数的销毁
【1】全局函数的销毁
- 重新定义同名函数
- del 语句删除函数名变量
- 程序结束
【2】局部变量的销毁
- 重新在上级作用域定义同名函数
- del 语句删除函数名称,函数对象的引用计数减一
- 上级作用域销毁时
6、函数执行流程(压栈过程):可以解释函数局部变量在函数调用是创建,在调用结束消亡;
【1】全局内存中生成 foo1 、foo2 、foo3 、main函数对象;
【2】main函数调用压栈;
【3】main 中查找内建函数 print 压栈,将常量字符串压栈,调用函数,弹出栈顶;(函数弹出的是返回值,此时栈底只有main 函数);
【4】main 中全局查找函数 foo1 压栈,将常量 100、101压栈,调用函数 foo1 ,创建栈帧。print 函数压栈,字符串和变量 b、b1 压栈,调用函数,弹出栈顶,返回值,foo1 函数结束调用弹出栈顶;
【5】main 中全局查找 foo2 压栈,将常量 200 压栈,调用 foo2 ,创建栈帧。
【6】foo3 函数压栈,变量 c 引用压栈,
【7】调用 foo3 ,创建栈帧。foo3 完成 print 函数调用后返回,弹出栈顶;
【8】foo2 恢复调用,执行 print 后,返回值。main 中 foo2 调用结束后弹出栈顶;
【9】继续执行 main 中的 print 函数调用,弹出栈顶。main 函数返回结束调用,弹出栈顶;
def foo1(b,b1=3):
print("foo1 called",b,b1)
def foo2(c):
foo3(c)
print("foo2 called",c)
def foo3(d):
print("foo3 called",d)
def main():
print("main called")
foo1(100,101)
foo2(200)
print("main ending")
main()