阅读目录:
1、函数的返回值
2、函数嵌套
3、作用域 ***
4、闭包**
5、nonlocal 关键字
6、默认值的作用域
7、变量名解析原则LEGB
8、函数的销毁
内容:
1、函数的返回值:
单条 return 语句:
1 def showplus(x): 2 print(x) 3 return x + 1 4 5 showplus(5) 6 7 8 def showplus(x): 9 print(x) 10 return x + 1 11 print(x+1) # 不会被执行,因为之前出现了return 并执行了 return语句 12 13 showplus(5)
多条 return 语句:
1 # def guess(x): 2 # if x > 3: 3 # return "> 3" 4 # else: 5 # return " <= 3" 6 # print(guess(10)) # > 3 7 8 def showplus(x): 9 print(x) 10 return x+1 11 return x+2 # 不被执行 12 showplus(5) 13 14 15 def fn(x): 16 for i in range(x): 17 if i > 3: 18 return i 19 else: 20 print('-', i) 21 22 print(fn(5)) # 4 23 print(fn(3)) # 24 25 # - 2 26 # None
总结:
- Python 函数使用 return 语句返回“返回值”
- 所有函数都有返回值,如果没有return 语句,隐式调用return None
- return 语句并不一定是函数的语句块的最后一条语句
- 一个函数可以存在多条return语句,但是只有一条被执行,乳沟哦没有一条return 语句被执行到隐式调用return None
- 如果有必要,可以显示调用return None,可以简写为return。
- 如果函数执行了return 语句,函数就会返回,当前被执行的return 语句之后的其他语句就不会被执行了
- 作用:结束函数调用,返回值
返回多个值 ???:return 是无法返回多个值的!,一次只能返回一个 ,返回类型根据具体情况而定!
1 def showplus(): 2 return [1,2,3] # [1, 2, 3] 3 def showplus(): 4 return 1,2,3 # (1, 2, 3) 5 6 print(showplus())
总结:
-
-
- 函数是 不能同时返回多个值
- return [12,3,4] 是指明返回一个列表,是一个列表对象
- return 1,,2,3 看似返回多个值,隐式的被python 封装成一个元组
-
1 def showplus(): 2 return 1,2,3 # (1, 2, 3) 3 4 print(showplus()) 5 a,b,c = showplus() 6 print(a, b, c) # 1 2 3
2、函数嵌套
在一个函数中定义了另外一个函数
1 def outer(): 2 def inner(): 3 print('inner') 4 print('outer') 5 inner() 6 outer() # 可以调用 outer() 7 inner() # 调用不到inner()
函数有可见范围,这就是“作用域”的概念。
内部函数不能再外部直接使用,会抛NameError 异常,因为 它 不可见。
3、作用域 ***
作用域:
一个标识符的可见范围,这就是标识符的作用域,一般常说的是变量的作用域。
常见作用域可能出现的问题:
1 # NO 1 2 x = 5 3 4 def foo(): 5 print(x) 6 7 foo() # 是可见的 8 9 # NO 2 10 x = 5 11 12 def foo(): 13 # UnboundLocalError: local variable 'x' referenced before assignment 14 x += 1 # 不可见的 15 print(x) 16 foo() 17 18 # NO 3 19 def foo(): 20 a = 10 21 print(x)# 4 22 x = 4 23 24 foo()# 是可以看到的,正常打印 25 26 # NO 4 27 x = 5 28 29 def show(): 30 ## 在函数内部再次使用 = 的时候,要注意,此时这个变量变为 local 变量 31 ## 赋值即被重新定义,即,赋值的时候,x 不知道多少,所以会报错 32 x += 1 # 报错, 33 print(x) 34 35 show() 36 37 # NO 6 38 x = 6 39 40 def show(): 41 print(x) 42 x += 1 # 跟上一个报错一样 # UnboundLocalError: local variable 'x' referenced before assignment 43 44 show() 45 46 # NO 7 47 x = 5 48 49 def show(x):# 同样报错,x此时还是局部变量,但是没有先定义,直接打印 50 print(x) 51 x += 1 52 print(x) 53 54 show(4) 55 56 # NO 8 57 x = 5 58 59 def show(x):# 相当于给了一个局部变量x = 4 60 print(x) # 4 61 x = 1 62 print(x) # 1 63 64 show(4) # 传参 65 print(x) # 5 66 67 # NO 9 68 x = 5 # 赋值即定义 69 70 def show(): 71 print(x) # 报错,赋值定义 应该 在打印之后 72 x = 1 73 74 show() 75 76 77 78 79 80 81 82
1 x = 5 2 3 def show(x):# 不报错!!!! 4 print(x) 5 x += 1 6 print(x) 7 8 show(4)
全局作用域:
在整个程序运行环境中可见
局部作用域:
在函数,类等内部是可见的
局部变量使用范围不能超过其所在的局部作用域
1 def fn(): 2 x = 1 3 def fn2(): 4 print(x) # 不可见 5 6 print(x) # 不可见
在嵌套结构里的作用域:
1 def outer1(): 2 o = 65 3 def inner(): 4 print('inner {}'.format(o)) 5 print(chr(o)) 6 print('outer {}'.format(o)) 7 inner() 8 9 outer1() 10 11 12 # outer 65 13 # inner 65 14 # A 15 16 def outer2(): 17 o = 65 18 def inner(): 19 o = 97 20 print('inner {}'.format(o)) 21 print(chr(o)) 22 print('outer {}'.format(o)) 23 inner() 24 25 outer2() 26 27 # outer 65 28 # inner 97 29 # a
从嵌套结构例子看出:
外层变量作用域在内层作用域可见
内层作用域 inner 中,如果定义了O = 97,相当于当前作用域 中重新定义了一个新的变量o,但是这个o并没用覆盖外层作用域outer中的o。
再看下面的代码:
1 # no 1 报错 2 # x = 5 3 # def foo(): # 报错 # UnboundLocalError: local variable 'x' referenced before assignment 4 # y = x + 1 #所以 x 是未定义的 5 # x += 1 # 还是之前的问题,赋值即重新定义,此时的x 是局部变量。 6 # print(x) 7 8 # foo() 9 10 # NO 2 11 x = 5 12 def foo(): 13 y = x + 1 14 print(x) # 5 15 16 foo() 17 18 # NO 3 报错 19 x = 5 20 def foo(): 21 y = x + 1 # UnboundLocalError: local variable 'x' referenced before assignment 22 x = 1 # 此时x 是局部变量,所以未定义,就先 加法,x+1 就出错了 23 print(x) # 5 24 25 foo()
事实上:
1 x = 5 2 def foo(): 3 x += 1 # 报错 4 解释: 5 x += 1 其实是 x = x + 1 6 相当于在foo 内部定义了一个局部变量x,那么foo()内部所有x 都是这个局部变量x了 7 到那时这个x还没有完成赋值,就被右边拿来做 +1 操作了
8 如何解决呢? 往下看 go go go
全局变量 global:
1 x = 5 2 def foo(): 3 global x 4 x += 1 5 6 foo() 7 8 使用global 关键字的变量,将foo 内的x 声明为使用外部的全局作用域中定义的x 9 全局作用域中必须有 x 的定义 10 如果全局作用域中没有x定义 再往下看 go go go
1 def foo(): 2 global q # 全局没定义 q ,所以报错 3 q += 1 4 print(q) 5 foo() 6 # NameError: name 'q' is not defined
global常见 问题:
1 # # NO 1 2 # x = 5 3 # def foo(): 4 # global x 5 # x += 1 6 # print(x) # 6 7 8 # foo() 9 10 # # NO 2 11 # x = 5 12 # def foo(): 13 # x += 1 14 # global x # SyntaxError: name 'x' is assigned to before global declaration 15 # print(x) 16 17 # foo() 18 19 # # NO 3 20 21 # def foo(): 22 # x = 100 # 这个x 并不是全局变量 23 # def bar(): 24 # global x # x 在全局没有定义,所以现在无法使用 25 # x += 1 26 # print(x) 27 # bar() 28 29 # foo() 30 # print(x) 31 32 33 # def foo(): 34 # x = 100 # 这个x 并不是全局变量 35 # def bar(): 36 # global x # x 就是一个全局变量了 37 # x = 1 #对全局变量定义赋值 38 # print(x) # 100 39 # bar() 40 41 # foo() 42 # print(x) # 1 43 44 # NO 4 45 46 ## x = 100 47 # def foo(): 48 # global x # 这两行相当于 全局的 x=100 49 # x = 100 # 50 # def bar(): 51 # global x # 52 # x += 1 53 # print(x) 54 # bar() 55 56 # foo() 57 # print(x) 58 59 # # 100 60 # # 101 61 62 63 # NO 5 64 65 # x = 100 66 # def foo(): 67 # def bar(): 68 # global x # 69 # x = 1 70 # x += 4 71 # bar() 72 # print(x) 73 74 75 # foo() 76 # print(x) 77 78 # 5 79 # 5 80 81 # def foo(): 82 # def bar(): 83 # global z # 84 # z = 1 85 # z += 4 86 87 # print(z) # NameError: name 'z' is not defined 88 # bar() # 所以这个 函数的调用位置很重要(在没有在最外面定义全局变量的时候) 89 90 # foo() 91 # print(z) 92 93 # NO 6 94 # x = 5 95 96 # def foo(): 97 # global x 98 # x = 10 99 # x += 1 100 # print(x) 101 # foo()# 如果不调用一下,下面的print(x)会报错,因为没有走 global x这一步 102 # print(x)
注:做这些实验注意解释器是否能记录之前的操作的变量,会有影响。
使用global 关键字的变量,将foo 内的x 声明为使用外部的全局作用域中定义的x
但是,x = 10 赋值即定义,在内部作用域为一个外部作用域的变量x 赋值,不是在内部作用域定义个新变量,所以 x += 1 不会报错,注意,这里 x 的作用域还是全局的。
global 总结:
-
- x += 1 这种是特殊形式产生的错误的原因?先引用后赋值,而python动态语言是赋值才算定义,才能被引用。解决办法,在这条语句前增加x=0之类的赋值语句,或者使用global 告诉内部作用域,去全局作用域查找变量定义。
- 内部作用域使用x= 5 之类的赋值语句 会重新定义局部作用域的变量想,但是,一旦这个作用域中使用global声明x 为全局的,那么x=5相当于在未全局作用域的变量x赋值。
global 使用原则:
-
- 外部作用域变量会内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离。
- 如果函数乣使用外部全局变量,请使用函数的形参传参解决。
- 一句话,不用global,
4、闭包**
自由变量:未在本地作用域中定义的变量,例如定义在内层函数外的外层函数的作用域中的变量。
闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包,
1 def foo(): 2 s = 100 # 自由变量 3 def bar(): # 闭包 4 print(1,s)# 100 5 bar() 6 print(s)# 100 7 foo() 8 print(s) # name 's' is not defined
1 def counter(): 2 c = [0] # 因为这里是引用类型 3 def inc(): 4 c[0] += 1 # 所以这里不出错 5 return c[0] 6 return inc 7 foo = counter() # inc的内存地址 8 print(foo(), foo()) 9 c = 100 10 print(foo()) 11 12 # 1 2 13 # 3 14 15 def counter(): 16 c = 2 17 def inc(): 18 c += 1 # 报错,c 是局部变量,未定义就使用 19 return c 20 return inc 21 foo = counter() # inc的内存地址 22 print(foo(), foo()) 23 c = 100 24 print(foo())
5、nonlocal 关键字:绝对不会去全局中找
使用nonlocal 关键字,将变量标记为不在本地作用域定义,而在上级的某一级局部作用域中定义,但不能是全局作用域中定义
1 def counter(): 2 c = 0 3 def inc(): 4 nonlocal c 5 c += 1 # 内层函数用到外层函数的自由变量,形成闭包 6 return c 7 # print(c) 8 return inc 9 10 foo = counter() 11 print(foo()) # 1 12 print(foo()) # 2
1 def counter(c): 2 def inc(): 3 nonlocal c 4 c += 1 # 内层函数用到外层函数的自由变量,形成闭包 5 return c 6 # print(c) 7 return inc 8 9 foo = counter(100) 10 print(foo()) # 1 11 print(foo()) # 2
count 是外层函数的局部变量,被内部函数引用
内部函数使用 nonlocal 关键字声明count 变量在上级作用域而非本地作用域中定义
1 a = 100 # 2 def counter(): 3 nonlocal a # 不能去全局中找 4 a += 1 5 print(a) 6 c = 0 7 def inc(): 8 nonlocal c 9 c += 1 # 内层函数用到外层函数的自由变量,形成闭包 10 return c 11 # print(c) 12 return inc 13 14 foo = counter() 15 print(foo()) # 1 16 print(foo()) # 2
6、默认值的作用域:
举例 1:
1 # NO 1 2 def foo(xyz=1): 3 print(xyz) 4 5 foo() # 1 6 foo() # 1 7 print(xyz) # 局部变量 8 9 # NO 2 10 11 def foo(xyz = [1]): 12 print(xyz,'-') 13 xyz.append(1) 14 print(xyz) 15 16 foo() # 使用了缺省值 17 foo() 18 19 # [1] - 20 # [1, 1] 21 # [1, 1] - 22 # [1, 1, 1] 23 24 NO 3 25 def foo(xyz = [1]): 26 print(xyz,'-') 27 xyz.append(1) 28 print(xyz) 29 30 foo([1]) #使用了实参 31 foo([1]) 32 foo() 33 foo() 34 35 # [1] - 36 # [1, 1] 37 # [1] - 38 # [1, 1] 39 # [1] - 40 # [1, 1] 41 # [1, 1] - 42 # [1, 1, 1] 43 44 # NO 4 45 46 def bar(xyz=1, a=2, c=(1,)): 47 print(xyz) 48 xyz += 1 49 print(xyz) 50 51 def foo(xyz = [1]):# 这个是将引用类型赋给xyz,所以要改全改,(注意引用类型)
相当于 xyz = foo.__defaults__[0] 而 foo.__defaults__[0] = [1]
52 print(xyz,'-') 53 xyz += [1] 54 print(xyz) 55 56 # 缺省值的本质:缺省值放在一个对象的特殊属性中,以元组保存(元组不可变,所以记录了顺序) 57 print(bar.__defaults__) 58 print(foo.__defaults__) 59 60 # (1, 2, (1,)) 61 # ([1],)
举例 2:
1 # NO 1 2 def foo(xyz=[] , u= 'abc', z=123): 3 xyz.append(1) 4 return xyz 5 6 print(foo(), id(foo)) 7 print(foo.__defaults__) 8 print(foo(), id(foo)) 9 print(foo.__defaults__) 10 11 # [1] 84351856 12 # ([1], 'abc', 123) 13 # [1, 1] 84351856 14 # ([1, 1], 'abc', 123) 15 16 ''' 17 函数地址并没有改变,就是说函数这个对象的没有变,调用它,他的属性__defaults__ 中使用元组保存默认值 18 19 xyz 默认值 是引用类型,引用类型的元素要变动,并不是元组的变化 20 '''
举例3 :
1 def bar(xyz,b = {} ,a=2,*args,m=3,n=[],**kwargs): 2 pass 3 print(bar.__defaults__) 4 print(bar.__kwdefaults__) 5 6 # ({}, 2) 7 # {'m': 3, 'n': []} # 保存所有的keyword-only 参数默认值
小结:
-
-
- 使用可变类型作为默认值,就可能修改这个默认值(引用类型)
- 有时候这个特性是好的,相对而言
- 如何做到按需改变呢? 往下看,go go go
-
举例4 :完全规避之前的效果!!!!!
1 def foo(xyz=[], u='abc' , z=123): 2 print(xyz,'-') 3 #xyz = foo.__defaults__[0] 4 xyz= xyz[:] # 浅拷贝 生成新的 [] 5 # 之前混淆了,是因为想成 列表内的引用类型了,所以觉得缺省值也会变。 6 xyz.append(1) 7 print(xyz) 8 9 10 foo() 11 print(foo.__defaults__) 12 print('----------------------------') 13 14 foo() 15 print(foo.__defaults__) 16 print('----------------------------') 17 18 foo([10]) 19 print(foo.__defaults__) 20 print('----------------------------') 21 22 foo([10 ,5]) 23 print(foo.__defaults__) 24 25 # [] - 26 # [1] 27 # ([], 'abc', 123) 28 # ---------------------------- 29 # [] - 30 # [1] 31 # ([], 'abc', 123) 32 # ---------------------------- 33 # [10] - 34 # [10, 1] 35 # ([], 'abc', 123) 36 # ---------------------------- 37 # [10, 5] - 38 # [10, 5, 1] 39 # ([], 'abc', 123) 40 ''' 41 1、函数体,不改变默认值,也就是说不会因为函数体内做了重新赋值,就会改变默认值 42 2、xyz都是传入参数或者默认参数的副本,如果就想修改原参数,无能为力 43 '''
举例 5:
1 def foo(xyz=None,u='abc',z=123): 2 if xyz is None: 3 xyz = [] 4 xyz.append(1) 5 print(xyz) 6 7 foo() 8 print(foo.__defaults__) 9 print('----------------------------') 10 11 foo() 12 print(foo.__defaults__) 13 print('----------------------------') 14 15 foo([10]) 16 print(foo.__defaults__) 17 print('----------------------------') 18 19 foo([10 ,5]) 20 print(foo.__defaults__) 21 22 # [1] 23 # (None, 'abc', 123) 24 # ---------------------------- 25 # [1] 26 # (None, 'abc', 123) 27 # ---------------------------- 28 # [10, 1] 29 # (None, 'abc', 123) 30 # ---------------------------- 31 # [10, 5, 1] 32 # (None, 'abc', 123) 33 34 ''' 35 使用不可变类型默认值 36 如果使用缺省值None 就创建一个列表 37 如果传入一个列表,就修改这个列表 38 '''
总结:举例4,举例5 通过副本或者 None 不会修改默认参数 ,做到按需修改~~~
第一种方法:使用copy ,创建一个新的对象,永远不能改变传入的从参数
第二种方法:
通过值的判断就可以灵活的选择创建或者修改传入的对象
这种方式灵活
很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,可以说这是一种惯用法
7、变量名解析原则LEGB
Local:本地作用域,局部作用域的 local 命名空间,函数调用时创建,调用结束消亡。
Enclosing:python2.2 时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
Global:全局作用域,即一个模块的命名空间,模块被import时创建,解释器退出时消亡
Build-in:内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡,如:print(open),printhe open都是内置的变量
8、函数的销毁:
全局函数:
同名函数覆盖
del 函数名 删除函数标识符,而非函数对象,引用计数减一。
程序结束时
局部函数:
重新在上级作用域定义同名函数
del 语句删除函数名称,函数对象的引用计数减一
上级作用域销毁时
注意点:
1 def f(n=[1]): 2 n += [1] # 在原来列表修改 这个跟append的效果是一样的!!! 3 4 def f(n=[1]) 5 n = n + [1] # 返回新的列表 6 7 所以两者是不同的,类似结合 |= , &= 等
def outer():def inner():print('inner')print('outer')inner()outer()inner()