Python基础之(函数进阶)
一、命名空间和作用域
1.1、命名空间
先来看个例子:
a=123 def test(a): m=321 print(a) #不报错 print(m) #报错了
上面为什么会报错呢?现在我们来分析一下python内部的原理是怎么样:
我们首先回忆一下Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟出一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读入内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
我们给这个‘存放名字与值的关系’的空间起了一个名字-------命名空间。代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间;在函数的运行中开辟的临时的空间叫做局部命名空间。
命名空间一共可以分为三种:全局命名空间、局部命名空间、内置命名空间
(ps:内置命名空间中存放了python解释器为我们提供的名字:input,print,str,list,tuple...它们都是我们熟悉的,拿过来就可以用的方法)
三种命名空间之间的加载与取值顺序:
加载顺序:内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)
取值顺序:
在局部调用:局部命名空间->全局命名空间->内置命名空间
在全局调用:全局命名空间->内置命名空间
综上所述,在找寻变量时,从小范围,一层一层到大范围去找寻。由此可见例子中的a=123为全局变量,当在函数中调用的时候是没有问题的,而m=321为局部变量,在全局中进行打印的时候就会报错,找不到m值
1.2、作用域
作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域。
全局作用域:包含内置名称空间、全局名称空间,在整个文件的任意位置都能被引用、全局有效
局部作用域:局部名称空间,只能在局部范围内生效
globals和locals方法(查看全局和局部命名空间)
ps:globals和locals都在全局的时候两者一样,当两者都处于局部的时候globals为全局,locals为局部
print(globals()) print(locals()) #全局 def func(): a = 12 b = 20 print(locals()) print(globals()) #局部 func()
global关键字
- 声明一个全局变量
- 在局部作用域想要对全局作用域的全局变量进行修改时,需要用到 global(限于字符串,数字)。
a=1 def fun(): global a a=2 fun() print(a)
执行结果:
ps:对可变数据类型(list,dict,set)可以直接引用不用通过global
li=[1,2,3] di={"a":1,"b":2} def fun(): li.append(4) di["c"]=3 fun() print(li,di)
执行结果:
nonlocal 关键字 (python3中)
适用于嵌套函数中内部函数修改外部变量的值(上一层)
def fun(): a = 2 def fun2(): nonlocal a a = a + 20 print(a) fun2() print(a) fun()
执行结果:
二、函数的嵌套和作用域链
2.1、函数的嵌套调用
def fun(a,b): if a>b: v=a else: v=b return v def fun1(x,y,z): v=fun(x,y) v1=fun(v,z) return v1 print(fun1(2,-1,5)) #结果为:5
2.2、函数的嵌套定义
def fun(): def fun1(): print("in the fun1") fun1() #此处对fun1进行调用,如不调用即是在fun中定义了一个fun1的函数 fun()
2.3、作用域链
函数的作用域链:小范围作用域可以使用大范围的变量,(只是查看,如果需要修改全局的使用global,局部上层使用nonlocal)但是反之不行,他是单向的。(针对不可变数据类型)
三、函数名本质
函数名本质上就是内存地址
3.1、函数名可以被引用
def func(): print('in func') f = func print(f)
3.2、函数名可以被当作容器类型的元素
def f1(): print('f1') def f2(): print('f2') d = {'f1':f1,'f2':f2} d["f1"]() #调用
3.3、函数名可以当作函数的参数和返回值
def f1(): print('f1') def func1(argv): argv() return argv f = func1(f1) f()
一句话:函数是第一类对象,即函数可以当作数据传递 (不明白?那就把函数当变量用)
四、闭包函数
内部函数包含对外部作用域而非全局作用域变量的引用,该内部函数称为闭包函数
def fun(): a=123 def inner(): print(a)
由于有了作用域的关系,我们就不能拿到函数内部的变量和函数了。如果我们就是想拿怎么办呢?返回呀!(这才是闭包函数的常用方法)
def fun(): a=123 def inner(): print(a) return inner f=fun() f() #调用返回的inner函数的内存地址
闭包函数的例子:
from urllib.request import urlopen def fun(url): def get(): return urlopen(url).read() return get sina=fun('http://www.sina.cn') print(sina().decode()) #sina首页的源码
判断是否是闭包函数:__closure__
def fun(): a=123 def inner(): print(a) print(inner.__closure__) fun() print(fun.__closure__)
执行结果: