函数进阶、命名空间和作用域
函数名的本质
函数名本质上就是函数的内存地址
1.可以被引用
def func(): print('in func') f = func print(f)
2.可以被当作容器类型的元素
def f1(): print('f1') def f2(): print('f2') def f3(): print('f3') l = [f1,f2,f3] d = {'f1':f1,'f2':f2,'f3':f3} #调用 l[0]() d['f2']()
3.可以当作函数的参数和返回值
def func(): print('func') def func1(f): print('func2') return f f = func f2 = func1(f) f2()
高阶函数(函数的嵌套)
只要遇见了函数名+()就是函数的调用. 如果没有就不是函数的调用。
# 例1: def func1(): print('in func1') print(3) def func2(): print('in func2') print(4) func1() print(1) func2() print(2) # 例2: def func1(): print('in func1') print(3) def func2(): print('in func2') func1() print(4) print(1) func2() print(2) # 例3: def fun2(): print(2) def fun3(): print(6) print(4) fun3() print(8) print(3) fun2() print(5)
函数的命名空间
在python解释器开始执行之后, 就会在内存中开辟一个空间, 每当遇到一个变量的时候, 就把变量名和值之间的关系记录下来, 但是当遇到函数定义的时候, 解释器只是把函数名读入内存, 表示这个函数存在了, 至于函数内部的变量和逻辑, 解释器是不关心的. 也就是说一开始的时候函数只是加载进来, 仅此而已, 只有当函数被调用和访问的时候, 解释器才会根据函数内部声明的变量来进行开辟变量的内部空间. 随着函数执行完毕, 这些函数内部变量占用的空间也会随着函数执行完毕而被清空.
我们首先回忆一下Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟里一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读如内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。
等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量回储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
我们给这个‘存放名字与值的关系’的空间起了一个名字-------命名空间。
代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间;
在函数的运行中开辟的临时的空间叫做局部命名空间也叫做临时名称空间。
总结:
1. 全局命名空间--> 我们直接在py文件中, 函数外声明的变量都属于全局命名空间
2. 局部命名空间--> 在函数中声明的变量会放在局部命名空间
3. 内置命名空间--> 存放python解释器为我们提供的名字, list, tuple, str, int这些都是内置命名空间
三种命名空间之间的加载与取值顺序:
加载顺序:内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)
取值顺序:
取值顺序就是引用一个变量,先从哪一个空间开始引用。这个有一个关键点:从哪个空间开始引用这个变量。我们分别举例说明:
# 如果你在全局名称空间引用一个变量,先从全局名称空间引用,全局名# 称空间如果没有,才会向内置名称空间引用。 input = 666 print(input) # 666 # 如果你在局部名称空间引用一个变量,先从局部名称空间引用, # 局部名称空间如果没有,才会向全局名称空间引用,全局名称空间在没有,就会向内置名称空间引用。 input = 666 print(input) # 666 input = 666 def func(): input = 111 print(input) # 111 func()
所以空间的取值顺序与加载顺序是相反的,取值顺序满足的就近原则,从小范围到大范围一层一层的逐步引用。
作用域
作用域就是作用范围, 按照生效范围来看分为全局作用域和局部作用域。
全局作用域: 包含内置命名空间和全局命名空间. 在整个文件的任何位置都可以使用(遵循 从上到下逐⾏执行)。
局部作用域: 在函数内部可以使用。
globals():全局作用域
globals(): 以字典的形式返回全局作用域所有的变量对应关系。
# 在全局作用域下打印,则他们获取的都是全局作用域的所有的内容。 a = 1 b = 2 print(globals()) ''' {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000210FB27B438>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/学习资料/Python学习/day11/globals和locals.py', '__cached__': None, 'a': 1, 'b': 2} '''
locals():局部作用域
locals(): 以字典的形式返回当前作用域的变量的对应关系。
# 在全局作用域下打印,则他们获取的都是全局作用域的所有的内容。 a = 2 b = 3 print(globals()) print(locals()) ''' {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001806E50C0B8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/学习资料/Python学习/day11/globals和locals.py', '__cached__': None, 'a': 2, 'b': 3} ''' # 在局部作用域中打印。 a = 2 b = 3 def foo(): c = 3 print(globals()) # 和上面一样,还是全局作用域的内容 print(locals()) # {'c': 3} foo()
关键字:global、nonlocal
global
a = 1 def func(): print(a) func() a = 1 def func(): a += 1 # 报错 func()
局部作用域对全局作用域的变量(此变量只能是不可变的数据类型)只能进行引用,而不能进行改变,只要改变就会报错,但是有些时候,我们程序中会遇到局部作用域去改变全局作用域的一些变量的需求,这怎么做呢?这就得用到关键字global:
global第一个功能:在局部作用域中可以更改全局作用域的变量。
count = 1 def search(): global count count = 2 search() print(count) # 2
利用global在局部作用域也可以声明一个全局变量。
def func(): global a a = 3 func() print(a) # 3
所以global关键字有两个作用:
1,声明一个全局变量。
2,在局部作用域想要对全局作用域的全局变量进行修改时,需要用到 global(限于字符串,数字)。
nonlocal
nonlocal是python3x新加的功能,与global用法差不多,就是在局部作用域如果想对父级作用域的变量进行改变时,需要用到nonlocal。
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()
nonlocal的总结:
1,不能更改全局变量。
2,在局部作用域中,对父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。