函数的进阶
名称空间
在python解释器开始执行之后, 就会在内存中开辟一个空间, 每当遇到一个变量的时候, 就把变量名和值之间的关系记录下来, 但是当遇到函数定义的时候, 解释器只是把函数名读入内存, 表示这个函数存在了, 至于函数内部的变量和逻辑, 解释器是不关心的. 也就是说一开始的时候函数只是加载进来, 仅此而已, 只有当函数被调用和访问的时候, 解释器才会根据函数内部声明的变量来进行开辟变量的内部空间. 随着函数执行完毕, 这些函数内部变量占用的空间也会随着函数执行完毕而被清空。
我们首先回忆一下Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟里一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读如内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量回储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
我们给这个‘存放名字与值的关系’的空间起了一个名字-------命名空间。
代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间;
在函数的运行中开辟的临时的空间叫做局部命名空间也叫做临时名称空间。
现在我们知道了,py文件中,存放变量与值的关系的一个空间叫做全局名称空间,而当执行一个函数时,内存中会临时开辟一个空间,临时存放函数中的变量与值的关系,这个叫做临时名称空间,或者局部名称空间。
其实python还有一个空间叫做内置名称空间:内置名称空间存放的就是一些内置函数等拿来即用的特殊的变量:input,print,list等等
命名空间
内置命名空间
就是python解释器一启动就可以使用的名字存储在内置命名空间中
内置的名字在启动解释器的时候被加载进内存里
全局命名空间---我们写的代码但不是函数中的代码
- 是程序从上到下被执行的过程中一次加载内存的
- 放置了我们设置的所有变量名和函数名
局部命名空间
- 就是函数内部定义的名字
- 当调用函数的时候才会产生这个名称的空间,随着函数执行的结束,这个命名空间被回收。
加载顺序
所谓的加载顺序,就是这三个空间加载到内存的先后顺序,也就是这个三个空间在内存中创建的先后顺序,你想想他们能是同时创建么?肯定不是的,那么谁先谁后呢?我们捋顺一下:在启动python解释器之后,即使没有创建任何的变量或者函数,还是会有一些函数直接可以用的比如abs(-1),max(1,3)等等,在启动Python解释器的时候,就已经导入到内存当中供我们使用,所以肯定是先加载内置名称空间,然后就开始从文件的最上面向下一行一行执行,此时如果遇到了初始化变量,就会创建全局名称空间,将这些对应关系存放进去,然后遇到了函数执行时,在内存中临时开辟一个空间,加载函数中的一些变量等等。所以这三个空间的加载顺序为:内置命名空间(程序运行伊始加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载。
取值顺序
取值顺序就是引用一个变量,先从哪一个空间开始引用。这个有一个关键点:从哪个空间开始引用这个变量。
- 如果你在全局名称空间引用一个变量,先从全局名称空间引用,全局名# 称空间如果没有,才会向内置名称空间引用。
- 如果你在局部名称空间引用一个变量,先从局部名称空间引用
- 局部名称空间如果没有,才会向全局名称空间引用,全局名称空间在没有,就会向内置名称空间引用。
所以空间的取值顺序与加载顺序是相反的,取值顺序满足的就近原则,从小范围到大范围一层一层的逐步引用。
- 在局部可以使用全局,内置命名空间中的名字
- 在全局,可以使用内置命名空间的名字,但不能用局部中使用
- 在内置:不能使用局部和全局的名字
- 在正常情况下,直接使用内置的名字
- 当我们在全局定义了和内置名字空间中同名的名字时,会使用全局的名字
- 当我自己有的时候 就不找上一级要了
- 如果上一级没有,就找上一级要,都没有的话就报错。
- 依赖倒置原则
作用域
全局作用域>作用在全局>内置和全局名字空间中的名字都属于全局作用域 globals()
局部作用域>作用在局部>函数(局部名字空间中的名字属于局部作用域)locals()
对于不可变数据类型,在局部可以查看全局作用域中的变量
但是不能直接修改
如果想要修改,需要在程序的一开始添加global声明
如果在一个函数内声明了一个global变量,那么这个变量在局部的所有的操作将对全局变量有效。
函数名的本质
函数名就是一个变量,具有变量的功能。可以被赋值;但是作为函数名它也有特殊的功能就是加上()就会执行对应的函数,所以我们可以把函数名当作一个特殊的变量。
def f1(): pass print(f1) #<function f1 at 0x0000021198E7BB70>
函数名可以赋值给其他变量
def f1(): print(‘666‘) a = f1 #把函数名当成一个变量赋值给另外一个变量 a() #函数调用f1() #通过变量的赋值,变量a 和变量f1都指向这个函数的内存地址,那么a() 当然可以执行这个函数了。
函数名也可以当作容器类的元素
a = ‘jim‘ b= ‘bob‘ c = ‘ajian‘ l = [a,b,c] for i in l: print(i) # jim bob ajian def func1(): print(‘1‘) def func2(): print(‘2‘) def func3(): print(‘3‘) l = [func1,func2,func3] for i in l: i()
函数名可以当作函数的参数
def f1(): print(1) def f2(f): print(2) f() f2(f1)
函数名作为函数的返回值
def f1(): print(1) def f2(f): print(2) return f ret = f2(f1) ret()
闭包
嵌套的函数,内部函数调用外部函数的变量
def f1(): s = ‘1234‘ def f2(): print(len(s)) return f2 #返回函数的名字 res = f1() res()
判断闭包函数的方法 closure
#输出的__closure__有cell元素 :是闭包函数 def f1(): s = ‘1234‘ def f2(): print(len(s)) print(f2.__closure__) return f2 res = f1() res() #输出的__closure__为None :不是闭包函数 s = ‘1234‘ def f1(): def f2(): print(len(s)) print(f2.__closure__) return f2 res = f1() res()