函数进阶-(命名空间、作用域、函数嵌套、作用域链、函数名本质、闭包)

函数的进阶

  • 由来

  • 命名空间

  • 函数作用域

  • 函数嵌套

  • 作用域链

  • 函数名本质

  • 闭包

一、由来

  首先我们先来看一个代码:

>>> def zjk_max():
...     a = 1
...     b = 2
...     print(a,b)
... 
>>> zjk_max()
1 2
>>> print(a,b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

  什么鬼?竟然报错了,错误是变量a没有被定义,可是我明明定义了a量等于1啊,这是咋回事呢。

  那么这里我们要捋一下python的代码在运行中遇到函数时是怎么做的。

#1.当python解释器开始执行之后,就在内存中开辟了一个空间;
#2.每当遇到一个变量的时候,就把变量名和值之间的对应关系记录下来;
#3.但是,当遇到函数定义的时候,解释器只是象征性的将函数名读入内存,表示知道这个函数的存在了,至于函数内部的变量和逻辑解释器根本不关心;
#4.等执行到函数调用时,python解释器会再开辟一块内存来存储这个函数里的内容,这个时候才关注函数里有哪些变量,而函数中的变量会存储在新开辟的内存中,函数中的变量只能在函数的内部使用,并且会随着函数执行完毕,这块内存中的所有的内容也会被清空;

  我们给这个“存放名字和值的关系的空间起了一个名称”--------叫做命名空间;

  代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间,在函数的运行中开辟的临时的空间叫做局部命名空间

二、命名空间

1.定义:

  命名空间就是存在着名字和值绑定关系的地方;

  python之禅中说过:命名空间是一种绝妙的理念,让我们尽情的使用发挥吧;

2.种类(命名空间有三种):

  a.内置命名空间 -----> (python解释器)

    就是python解释器一启动就可以使用的名字存储在内置命名空间中;

    内置的名字在启动解释器的时候被加载进内存里,比如:input、print、list、tuple等等;

  b.全局命名空间 ------> (就是我们写的代码但不是函数中的代码)

    是在程序从上到下被执行的过程中依次加载进内存的;

    放置了我们设置的所有变量名和函数名

  c.局部命名空间 -------> (函数)

    就是函数内部定义的名字;

    当调用函数的时候,才会产生这个名称空间,随着函数执行的结束,这个命名空间就又消失了;

  说明:

#1.在局部:可以使用全局、内置命名空间中的名字;
#2.在全局:可以使用内置命名空间中的名字,但是不能使用局部中的名字;因为局部的代码随之函数执行完毕后就被清除了;
#3.在内置,不能使用局部和全局的名字;启动的时候就加载了,当然不会使用局部和全局了,那时的代码都还没有加载呢,何谈调用

  注意:

#1.在正常的情况下,直接使用内置的名字;
#2.当我们在全局定义了和内置名字空间中同名的名字时,会使用全局的名字,这是因为,当我这层空间有了我想要的变量时,我就不会再向上级去寻要了,如果我自己当前这个空间没有,那么我就要找我上一层空间去要,上一层没有的话去上上层要,如果内置空间里名字都不存在,就会报错;
#3.多个函数拥有多个独立的局部名字空间,不相互共享;

3.三种命名空间之间的加载与取值顺序:

  加载顺序(从小到大):内置命名空间(程序运行前加载) --------> 全局命名空间(程序运行中:从上到下加载) --------> 局部命名空间(程序运行中:调用时才加载)

  取值:

  在局部调用:先检查局部内有没有所需要的,如果局部没有则去上一级(全局命名空间)查找,如果上一级还没有则去内置命名空间查找;

  在全局调用:首先在全局命名空间查找,如果没有则去内置命名空间查找;

4.这三个种类的图解:

三、函数作用域

1.定义:

  作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域;

  全局作用域:包含内置命名空间、全局命名空间,在整个文件的任意位置都能被引用、全局有效;

  局部作用域:局部命名空间,只能在局部范围内生效;

2.查看全局作用域和局部作用域的两个方法:

  globals和locals:

  globals:查看全局的作用域,以字典的形式将全局变量显示出来;

  locals:查看局部的作用域,以字典的形式将局部变量显示出来;

  例如

#-----------------在局部命名空间查看 globas 和 locals -----------------
>>> name = "python"
>>> def cat():
...     a = 1
...     b = 2
...     c = "zjk"
...     print(locals())
...     print(globals())
... 
>>> cat()
{'a': 1, 'b': 2, 'c': 'zjk'}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'cat': <function cat at 0x7ff74ba79268>, 'name': 'python'}


#------------------在全局命名空间查看 globas 和 locals -----------------
>>> a,b,c = "ABC"
>>> print(globals());print(locals())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': 'A', 'b': 'B', 'c': 'C'}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': 'A', 'b': 'B', 'c': 'C'}


#我们会发现在全局命名空间中,它的globs 和locals是一样的,这点大家都可以想清楚吧。

#总结:
#globals 永远打印全局的名字;
#locals 根据locals所在的位置进行输出;

  还有一点需要大家注意:

  对于变量为不可变数据类型来说, 在局部是可以查看全局作用域中的不可变类型的变量,但是不能直接修改;如果想要修改,需要在程序的一开始添加global声明;如果在一个局部(函数)内声明了一个global变量,那么这个变量在局部的所有操作将对全局的变量有效。

  例如:

#------------- 我们尝试在局部变量中对全局命名空间的一个不可变类型的a变量进行修改 -------------------------------
>>> a = "zjk"
>>> def func():
...     a+="_china"
... 
>>> func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in func
UnboundLocalError: local variable 'a' referenced before assignment
>>> print(a)
zjk
#可以发现在运行函数时直接报错了,并且全局命名空间的a变量并没有被修改;

#------------- 我们再尝试在局部变量中对全局命名空间的一个可变类型的b变量进行修改 -------------------------------
>>> b = ["A","B","C"]
>>> def func1():
...     b.append("D")
... 
>>> func1()
>>> print(b)
['A', 'B', 'C', 'D']
#可以发现,对于可变的全局命名空间里的变量,在局部命名空间中是可以被修改的;

#那么如果在局部命名空间对去全局命名空间中的不可变类型的变量进行修改呢?我们可以使用global关键字来修改;

#------------使用 global关键字修改不可变类型的全局命名空间变量 --------------
>>> a = "zjk"
>>> def func2():
...     global a
...     a+="_china"
... 
>>> func2()
>>> print(a)
zjk_china
#可以发现我们成功的对全局命名空间的不可变类型的变量进行了修改,但是这种在局部变量中通过global进行修改全局变量,这样的代码是不安全的,以后我们会说一下其他方法;
不可变类的全局命名空间变量如何在局部中修改

3.global关键字、nonlocal关键字:

  global:

  a.声明一个全局变量;

  b.在局部作用域想要对全局作用域的全局变量进行修改时,需要用到global(限于字符串、数字,也就是不可变类型);

def func():
    global a
    a = 3
func()
print(a)


count = 1
def search():
    global count
    count = 2
search()
print(count)

global关键字举例
global关键字举例

  注意:对可变数据类型(list,dict,set)可以直接引用不用通过global;这点我们在上面也强调过了,在此不再举例;

  nonlocal:

  a.不能修改全局变量;

  b.在局部作用域中,对父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变;

def add_b():
    b = 42
    def do_global():
        b = 10
        print(b)
        def dd_nonlocal():
            nonlocal b
            b = b + 20
            print(b)
        dd_nonlocal()
        print(b)
    do_global()
    print(b)
add_b()
----------------------显示结果-------------------------------------
10
30
30
42
nonlocal关键字举例

四、函数嵌套

  

  

猜你喜欢

转载自www.cnblogs.com/zhangjunkang/p/9446600.html