名字空间(namespace)
1)、名字(name)与对象(object)
对象:对象一块存储区域,用来存储值,同时包含对该值所支持的一系列方法,也包含一系列属性。
名字:每个名字对应一个对象,多个名字可以对应一个对象。这有点类似于别的语言中的别名。
2)、名字空间
名字空间:名字空间是用来专门存储名字与对象的对应关系的地方,在python中名字空间一般使用dict数据结构实现。
在python中,函数、模块等都有自己的命名空间:
局部命名空间(local namespace):即函数中定义的名称 —— 包括函数中的变量、参数、局部变量等;随函数而生,随函数而亡。
全局命名空间(global namespace):即模块中定义的名称 —— 包括模块中的变量、函数、类、参数、常量、导入(import)的模块等;随模块而生,随模块而亡。
内置命名空间(built-in namespace):即python内置的名称 —— 包括各种内置函数、Exception等;随解释器而生,随解释器而亡。
ps:实际上,class也会形成一个特殊的namespace。
而,当python需要使用变量时,会在上述命名空间中依次查找,顺序是:
局部命名空间,全局命名空间、内置命名空间。
同一命名空间中不能有重名,但不同命名空间可以。
- 可以通过locals()(获取局部namespace)、globals()(获取全局namespace) 函数来获取命名空间的值(字典),在程序的不同位置执行结果不一定一致,因为结果是针对当前位置来说的。
ps:因为python是一行一行执行的,当有新的名字与object进行bind的时候,就会将该名字与object的对应关系加入到相应的namespace。
作用域(scope)
作用域:可以理解为变量所起作用的范围,超出范围则某变量不能被使用。在python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则报错。Python 中只有模块(module),类(class)以及函数(def、lambda)才会产生新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会产生新的作用域的。
ps: python使用静态作用域(词法作用域),即标识符的作用域只与它声明的位置有关,一旦声明,则作用域确认。标识符在使用前必须声明,注意函数在被调用前必须声明。
作用域可以分为四种:
Local:最内层,包含局部变量,一般指的是函数内部的作用域;
Enclosing:包含非局部但是也不是全局的变量,主要是嵌套时,外层函数的变量,那么相对内层函数来说,嵌套的外层函数中的变量既不是局部变量也不是全局变量。
Global:全局变量,例如当前模块中的全局变量。
Build-in:内置变量。
查找顺序一般是:Local--->Enclosing--->Global--->Build-in
当查找到一个对应的变量后,就停止继续外层作用域查找。
那么查找的位置就是对应的作用域中的namespace(随着代码的执行,namespace不断改变,添加名字或者删除名字。)。
作用域图:
- 通过nonlocal可以将一个变量声明为外层作用域变量,即在一个内层作用域将一个name与外层作用域相关联,然后解释器就知道去外层作用域寻找该name对应的那个object了。
- 通过global可以将一个变量声明为全局作用域变量,即在任何地方将一个name与全局作用域相关联,然后解释器就知道去全局作用域寻找该name对应的那个object了。
#一个典型的例子:
def scope_test():
def do_local():
spam = "local spam" #local scope
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam" #outer scope,此时改变的scope_test中定义的spam的值。
def do_global():
global spam
spam = "global spam" #global scope,此时改变的是global scope中定义的spam的值。
spam = "test spam"
do_local() #改变的是local scope中的spam。
print("After local assignment:", spam) #scope_test中的spam。
do_nonlocal() #改变的是scope_test中的spam。
print("After nonlocal assignment:", spam) #scope_test中的spam。
do_global() #改变的是global scope中的spam。
print("After global assignment:", spam) #scope_test中的spam。
scope_test()
print("In global scope:", spam) #输出global scope中的spam。
运行结果:
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
#变量寻找的例子:
x=3
def g():
print(x) # local scope没有声明x,所以引用到global scope的x.
运行结果:3
x=3
def g():
x=2 # 定义并赋值本地变量x
print(x) # 引用本地变量x
运行结果:2
x=1
def g():
print(x)
x=2
g()
运行结果:出错。
python解释器运行python代码都是一行一行读入并运行的,但是对于函数来说,都是一次性读入,所以在上面的代码中,g()是被一次性读入的,并且解释器记住了g()定义了x=2,所以x就会引用到local scope中的变量x,但是x在使用前没有先声明(使用前必须先声明),所以出错。
#函数的两个例子
函数在声明定义阶段不会执行,因此可以使用未定义的名字,但是在运行之前,这些名字必须定义好,否则会出错。
x=1
def f():
x=3
g()
print("f:",x) # 3
def g():
print("g:",x) # 1
f()
print("main:",x) # 1
这里不会出错,因为在运行之前,我们已经定义了g()。
x=1
def f():
x=3
g()
print("f:",x)
f() # 报错
def g():
print("g:",x)
这里出错,这是因为f()已经运行了,但是g()还没有定义好,所以就出错了。