Python入门篇-返回值和作用域
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.返回值
1>.返回值概述
Python函数使用return语句返回“返回值”
所有函数都有返回值,如果没有return语句,隐式调用return None
return 语句并不一定是函数的语句块的最后一条语句
一个函数可以存在多个return语句,但是只有一条可以被执行。如果没有一条return语句被执行到,隐式调用return None
如果有必要,可以显示调用return None,可以简写为return
如果函数执行了return语句,函数就会返回,当前被执行的return语句之后的其它语句就不会被执行了
作用:结束函数调用、返回值
2>.函数单个值
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 def show(x): 9 for i in range(x): 10 if i > 3: 11 return i 12 else: 13 print("{} is not greater than 3".format(i)) 14 15 16 res1 = show(3) 17 18 print("*" * 20 + "我是分隔符" + "*" * 20) 19 20 res2 = show(5) 21 22 print("res1 = {}".format(res1)) 23 print("res2 = {}".format(res2)) 24 25 26 27 #以上代码执行结果如下: 28 0 is not greater than 3 29 1 is not greater than 3 30 2 is not greater than 3 31 ********************我是分隔符******************** 32 0 is not greater than 3 33 1 is not greater than 3 34 2 is not greater than 3 35 3 is not greater than 3 36 res1 = None 37 res2 = 4
3>.返回多个值
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :yinzhengjie #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ #EMAIL:y1053419035@qq.com """ 函数不能同时返回多个值,但是可用返回一个列表或者一个元组 """ """ 是指明返回一个列表,是一个列表对象 """ def returnList(): return [1,3,5] """ 看似返回多个值,隐式的被python封装成了一个元组 """ def returnTuple(): return [2,4,6] x,y,z = returnList() #使用解构提取更为方便 m,n,l = returnTuple() print(x,y,z,m,n,l) #以上代码执行结果如下: 1 3 5 2 4 6
二.作用域
1>.作用域概述
作用域:
一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域
全局作用域
在整个程序运行环境中都可见
局部作用域
在函数、类等内部可见
局部变量使用范围不能超过其所在的局部作用域
2>.嵌套结构观察作用域变化
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 """ 9 外层变量作用域在内层作用域可见 10 """ 11 def outer1(): 12 o = 65 13 def inner(): 14 print("inner {}".format(o)) 15 print(chr(o)) 16 print("outer {}".format(o)) 17 inner() 18 19 """ 20 内层作用域inner中,如果定义了o=97,相当于当前作用域中重新定义了一个新的变量o,但是这个o并没有覆盖外层作用域outer中的o 21 """ 22 def outer2(): # 23 o = 65 24 def inner(): 25 o = 97 26 print("inner {}".format(o)) 27 print(chr(o)) 28 print("outer {}".format(o)) 29 inner() 30 31 outer1() 32 33 print("*" * 20 + "我是分隔符" + "*" * 20) 34 35 outer2() 36 37 38 39 #以上代码执行结果如下: 40 outer 65 41 inner 65 42 A 43 ********************我是分隔符******************** 44 outer 65 45 inner 97 46 a
3>.赋值定义注意事项
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :yinzhengjie #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ #EMAIL:y1053419035@qq.com x = 10 #全局作用域变量x def foo(): """ “y = x + 1”这一行代码引用了全局作用域的x变量。 """ y = x + 10 """ 这一行代码会抛异常:“UnboundLocalError: local variable 'x' referenced before assignment”。 原因就是赋值定义导致的,“x += 1”其实是“x = x + 1”,相当于在foo内部定义一个局部变量x, 那么foo内部所有x都是这个局部变量x了,但是这个x还没有完成赋值,就被右边拿来做加1操作了,因此才会抛出该异常 简单的说,就是在定义x变量之前我们已经对该变量做了引用导致的报错。如何解决这个问题?我们可以引入全局变量global. """ # x += 1 print(x,y) foo() #以上代码输出结果如下: 10 20
4>.在局部变量修改全局变量
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :yinzhengjie #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ #EMAIL:y1053419035@qq.com x = 5 """ 使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x 全局作用域中必须有x的定义,如果全局作用域中没有定义x变量,则会报错:“NameError: name 'x' is not defined” """ def foo(): global x print("foo() : x = {}".format(x)) x += 10 print("foo() : x = {}".format(x)) print("调用foo()函数之前:x = {}".format(x)) foo() print("调用foo()函数之后:x = {}".format(x)) #以上代码输出结果如下: 调用foo()函数之前:x = 5 foo() : x = 5 foo() : x = 15 调用foo()函数之后:x = 15
5>. 在局部变量中声明全局变量
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 def foo(): 9 global x #使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x 10 x = 10 #x = 10 赋值即定义,在内部作用域为一个外部作用域的变量x赋值,不是在内部作用域定义一个新变量,所以x+=1不会报错。注意,这里x的作用域还是全局的 11 x += 20 12 print(x) 13 14 foo() 15 16 print(x) #注意,这个打印x变量一定要在先调用foo()函数,因为只有调用了foo()函数才会触发生成全局变量x,否则会抛异常:"NameError: name 'x' is not defined" 17 18 19 20 #以上代码输出结果如下: 21 30 22 30
6>.global总结
x+=1这种是特殊形式产生的错误的原因?先引用后赋值,而python动态语言是赋值才算定义,才能被引用。解决办法,在这条语句前增加x=0之类的赋值语句,或者使用global告诉内部作用域,去全局作用域查找变量定义
内部作用域使用x = 5之类的赋值语句会重新定义局部作用域使用的变量x,但是,一旦这个作用域中使用global声明x为全局的,那么x=5相当于在为全局作用域的变量x赋值
7>.global使用原则
外部作用域变量会内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
如果函数需要使用外部全局变量,请使用函数的形参传参解决
一句话:不用global。学习它就是为了深入理解变量作用域.
三.闭包
1>.闭包概述
自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量
闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是JavaScript
2>.闭包函数案例(这是Python2中实现闭包的方式,Python3还可以使用nonlocal关键字)
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 def counter(): 9 c = [0] 10 def inc(): 11 c[0] += 10 #注意,这行代码c[0] = c[0] + 10,这样操作并不会报错,因为并没有修改变量c,而是修改的c中的一个引用。 12 return c[0] 13 return inc #返回的是c变量的一个引用,即c[0] 14 15 foo = counter() 16 17 print(foo(),foo()) #每调用次都会对c[0] 对象加10,因此每次调用的结果不会发生改变 18 19 c = 100 #此处我们定了一个变量c,但对c[0]并不会产生印象哟,因此下面的操作依旧会对c[0]加10. 20 21 print(foo()) 22 23 24 25 #以上代码执行结果如下: 26 10 20 27 30
3>.nonlocal关键字
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 9 """ 10 使用了nonlocal关键字,将变量标记为不在本地作用域定义,而在上级的某一级局部作用域中定义,但不能是全局作用域中定义 11 count 是外层函数的局部变量,被内部函数引用 12 内部函数使用nonlocal关键字声明count变量在上级作用域而非本地作用域中定义 13 """ 14 15 def counter(): 16 count = 0 17 def inc(): 18 nonlocal count 19 count += 10 20 return count 21 return inc 22 23 foo = counter() 24 25 print(foo()) 26 print(foo()) 27 28 29 30 #以上代码执行结果如下: 31 10 32 20
四.默认值的作用域
1>.默认值的作用域
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 def foo(x=1): 8 return x 9 10 def bar(y=[]): 11 y.append(100) 12 return y 13 14 print("foo():{}, ID:{} ".format(foo(),id(foo()))) 15 print("foo():{}, ID:{} ".format(foo(),id(foo()))) 16 17 # print(x) #报错:NameError: name 'x' is not defined,即形参的作用域数据函数的局部变量 18 19 print("bar():{}, ID:{} ".format(bar(),id(bar()))) 20 print("bar():{}, ID:{} ".format(bar(),id(bar()))) 21 22 """ 23 为什么第二次调用foo函数打印的是[100,100]? 24 因为函数也是对象,python把函数的默认值放在了属性中,这个属性就伴随着这个函数对象的整个生命周期 25 查看bar.__defaults__属性 26 """ 27 print("bar.__defaults__:{}".format(bar.__defaults__)) 28 29 30 31 #以上代码执行结果如下: 32 foo():1, ID:1638886432 33 34 foo():1, ID:1638886432 35 36 bar():[100, 100], ID:42947656 37 38 bar():[100, 100, 100, 100], ID:42947656 39 40 bar.__defaults__:([100, 100, 100, 100],)
2>.非引用类型例子
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 def foo(w, u='yzj', *, z=123, zz=[456]): 9 u = 'jason' 10 z = 2019 11 zz.append(1) 12 print(w, u, z, zz) 13 14 print(foo.__defaults__) #属性__defaults__中使用元组保存所有位置参数默认值,它不会因为在函数体内使用了它而发生改变 15 16 foo('node101.yinzhengjie.org.cn') 17 18 print(foo.__kwdefaults__) #属性__kwdefaults__中使用字典保存所有keyword-only参数的默认值 19 20 21 22 #以上代码执行结果如下: 23 ('yzj',) 24 node101.yinzhengjie.org.cn jason 2019 [456, 1] 25 {'z': 123, 'zz': [456, 1]}
3>.使用影子拷贝创建一个新的对象,永远不能改变传入的参数
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 """ 8 使用影子拷贝创建一个新的对象,永远不能改变传入的参数 9 """ 10 def foo(x=[], y='jason', z=123): 11 x = x[:] # 影子拷贝 12 x.append(1) # x都是传入参数或者默认参数的副本,如果就想修改原参数,无能为力 13 print(x) 14 15 foo() 16 print(foo.__defaults__) 17 foo() 18 print(foo.__defaults__) 19 foo([10]) 20 print(foo.__defaults__) 21 foo([10,5]) 22 print(foo.__defaults__) 23 24 25 26 #以上代码执行结果如下: 27 [1] 28 ([], 'jason', 123) 29 [1] 30 ([], 'jason', 123) 31 [10, 1] 32 ([], 'jason', 123) 33 [10, 5, 1] 34 ([], 'jason', 123)
4>.使用不可变类型默认值
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :yinzhengjie #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ #EMAIL:y1053419035@qq.com """ 通过值的判断就可以灵活的选择创建或者修改传入对象 这种方式灵活,应用广泛 很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,可以说这是一种惯用法 """ def foo(x=None, y='jason', z=2019): if x is None: x = [] x.append(1) print(x) foo() print(foo.__defaults__) foo() print(foo.__defaults__) foo([10]) print(foo.__defaults__) foo([10,5]) print(foo.__defaults__) #以上代码执行结果如下: [1] (None, 'jason', 2019) [1] (None, 'jason', 2019) [10, 1] (None, 'jason', 2019) [10, 5, 1] (None, 'jason', 2019)
#!/usr/bin/env python #_*_conding:utf-8_*_ #@author :yinzhengjie #blog:http://www.cnblogs.com/yinzhengjie def foo(x=1,y="jason",z={},*args,m=100,n,**kwargs): print(x,y,z) #打印位置参数 print(m,n) #打印keyword-only参数 print(z.setdefault("Name","yinzhengjie")) #我们对z变量赋值 print("查看位置参数 : ",foo.__defaults__) print("查看keyword only参数 : ",foo.__kwdefaults__) foo(z={},n=200) #需要注意的是,这里我们给z传入了一个{}参数,并没有使用默认的"{}"参数哟!这一点大家不要搞混了 print("查看位置参数 : ",foo.__defaults__) print("查看keyword only参数 : ",foo.__kwdefaults__) foo(n=200) print("查看位置参数 : ",foo.__defaults__) #不难发现,由于我们没有给z传入变量,因此z使用了默认的"{}",而“foo.__defaults__”是不变的,但地址的引用对象里面的元素却发生了变化! print("查看keyword only参数 : ",foo.__kwdefaults__) #以上代码输出结果如下: 查看位置参数 : (1, 'jason', {}) 查看keyword only参数 : {'m': 100} 1 jason {} 100 200 yinzhengjie 查看位置参数 : (1, 'jason', {}) 查看keyword only参数 : {'m': 100} 1 jason {} 100 200 yinzhengjie 查看位置参数 : (1, 'jason', {'Name': 'yinzhengjie'}) 查看keyword only参数 : {'m': 100}
五.变量名解析原则LEGB
Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如print(open),print和open都是内置的变量
所以一个名词的查找顺序就是LEGB
关于函数的执行流程,博主推荐链接:http://pythontutor.com/visualize.html#mode=edit。
六.函数的销毁
定义一个函数就是生成一个函数对象,函数名指向的就是函数对象。
可以使用del语句删除函数,使其引用计数减1。
可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减1。
Python程序结束时,所有对象销毁。
函数也是对象,也不例外,是否销毁,还是看引用奇数是否减为0。
1>.全局函数销毁
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 """ 9 全局函数销毁 10 重新定义同名函数 11 del 语句删除函数对象 12 程序结束时 13 """ 14 15 16 17 def foo(x=[], y='jason', z=123): 18 x.append(100) 19 return x 20 21 print(foo(), id(foo), foo.__defaults__) 22 23 def foo(x=[], y='jason', z=123): 24 x.append(200) 25 return x 26 27 print(foo(), id(foo), foo.__defaults__) 28 29 del foo 30 31 # print(foo(), id(foo), foo.__defaults__) #由于我们已经使用del关键字销毁了foo函数,执行该行代码会抛异常:"NameError: name 'foo' is not defined" 32 33 34 35 #以上代码执行结果如下: 36 [100] 4136608 ([100], 'jason', 123) 37 [200] 35462136 ([200], 'jason', 123)
2>.局部函数销毁
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 """ 9 局部函数销毁 10 重新在上级作用域定义同名函数 11 del 语句删除函数名称,函数对象的引用计数减1 12 上级作用域销毁时 13 """ 14 def foo(x=[], u='jason', z=2019): 15 x.append(1) 16 def inner(a=10): 17 pass 18 print(inner) 19 def inner(a=100): 20 print(x) 21 print(inner) 22 return inner 23 24 25 bar = foo() 26 print(id(foo),id(bar), foo.__defaults__, bar.__defaults__) 27 del bar 28 # print(id(foo),id(bar), foo.__defaults__, bar.__defaults__) #由于我们已经使用del关键字销毁了bar函数,执行该行代码会抛异常:"NameError: name 'bar' is not defined" 29 30 31 #以上代码执行结果如下: 32 <function foo.<locals>.inner at 0x0000000001EB1BF8> 33 <function foo.<locals>.inner at 0x0000000001EB1C80> 34 4923040 32185472 ([1], 'jason', 2019) (100,)