Python-函数返回值,作用域,函数销毁,闭包

阅读目录:

  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

  总结:

    1. Python 函数使用 return 语句返回“返回值”
    2. 所有函数都有返回值,如果没有return 语句,隐式调用return None
    3. return 语句并不一定是函数的语句块的最后一条语句
    4. 一个函数可以存在多条return语句,但是只有一条被执行,乳沟哦没有一条return 语句被执行到隐式调用return None
    5. 如果有必要,可以显示调用return None,可以简写为return。
    6. 如果函数执行了return 语句,函数就会返回,当前被执行的return 语句之后的其他语句就不会被执行了
    7. 作用:结束函数调用,返回值

  返回多个值 ???: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     
test-作用域案例
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()
局部变量-test

      事实上:     

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-test

    :做这些实验注意解释器是否能记录之前的操作的变量,会有影响。

    使用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
nonlocal 不能去全局中找

  

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()

为什么要坚持,想一想当初!
原文地址:https://www.cnblogs.com/JerryZao/p/9526995.html