命名空间和作用域
一、命名空间
命名空间(Namespace)是从名称到对象的映射,一般用 Python 字典来实现。
为了解决项目中名字冲突的问题引入了命名空间的概念,命名空间可以嵌套。
1、命名空间分类:
内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 Exception 等。
全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)
2、命名空间查找顺序:
局部命名空间 -> 全局命名空间 -> 内置命名空间。如果找不到变量,它将放弃查找并引发一个 NameError 异常: NameError: name ‘x’ is not defined。
3、命名空间的生命周期:
命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。
因此,我们无法从外部命名空间访问内部命名空间的对象。
二、作用域
作用域就是一个程序可以直接访问命名空间的正文区域。
1、Python 的作用域
通常而言,变量的作用域从代码结构形式来看,有块级、函数、类、模块、包等由小到大的级别。但是在 Python 中,没有块级作用域,也就是类似 if/elif/else/ 语句块、for/while 语句块、try/except 语句块和 with 上下文管理器等等是不存在作用域概念的,他们等同于普通的语句。
if True: # if 语句块中变量没有作用域
x = 1
print(x)
def func(): # 函数中变量有作用域
a = 8
print(a)
NameError: name 'a' is not defined
通常,函数内部代码可以访问外部变量,而外部代码通常无法访问内部变量。
Python 的作用域一共有 4 层,分别是:
L (Local) 局部作用域,最内层,包含局部变量,比如一个函数/方法内部。
E (Enclosing) 闭包函数外的函数中,包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。
G (Global) 全局作用域,当前脚本的最外层,比如当前模块的全局变量。
B (Built-in) 内建作用域,包含了内建的变量/关键字等。
x = int(2.9) # 内建作用域,查找 int 函数
global_var = 0 # 全局作用域 global
def outer():
out_var = 1 # 闭包函数外的函数中
def inner():
inner_var = 2 # 局部作用域 local
Python 以 L –> E –> G –> B 的规则查找变量,即:从内向外找。如果这样还找不到,那就提示变量不存在的错误。
通常,函数内部代码可以访问外部变量,而外部代码通常无法访问内部变量,访问权限决定于这个变量是在哪里赋值的。
g_count = 0 # 全局作用域
def outer():
o_count = 1 # 闭包函数外的函数中
def inner():
i_count = 2 # 局部作用域
内置作用域是通过一个名为 builtin 的标准模块来实现的,但是这个变量名自身并没有放入内置作用域内,所以必须导入这个文件才能够使用它。
>>> import builtins
>>> dir(builtins)
2、全局变量和局部变量
函数内部定义的变量拥有一个局部作用域,被叫做局部变量,定义在函数外的拥有全局作用域的变量,被称为全局变量。(类、模块等同理)
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。
所谓的局部变量是相对的。局部变量也有可能是更小范围内的变量的外部变量。
a = 1 # 全局变量 (公有)
def func():
b = 2 # 局部变量 (私有)
print(a,b)
# 可访问全局变量 a 和局部变量 b,无法访问它内部的 c。
# print(c)
def inner():
c = 3 # 局部变量
print(a) # 可以访问全局变量 a
print(b) # b 对于 inner 函数来说,就是外部变量。
print(c)
3、global 和 nonlocal 关键字
当内部作用域想修改外部作用域的变量时,需要 global 和 nonlocal 关键字来声明。
例 1:
total = 0 # total是一个全局变量
def plus(arg1, arg2):
# global total # 使用 global 关键字声明此处的 total 引用外部的 total
total = arg1 + arg2
print("total = {},id = {}".format(total, id(total)))
return total
plus(10, 20)
print("全局变量 total = {} id = {}".format(total, id(total)))
函数 plus 内部通过 total = arg1 + arg2 语句,新建了一个局部变量 total,它和外面的全局变量 total 是两码事。如果想要在函数内部修改外面的全局变量 total?使用 global 关键字声明!
global:指定当前变量使用外部的全局变量
例 2:
def outer():
# global a
# nonlocal a
a = 2
print("函数 outer 调用之时闭包外部的变量 a 的内存地址:", a, id(a))
def inner():
# global a
# nonlocal a
a = 3
print("函数 inner 调用之后闭包内部变量 a 的内存地址:", a, id(a))
inner()
print("函数 inner 调用之后,闭包外部的变量 a 的内存地址:", a, id(a))
a = 1
print("global a 的内存地址:", a, id(a))
outer()
print("函数 outer 执行完毕,全局变量 a 的内存地址: ", a, id(a))
三个 a 各是各的 a,各自有不同的内存地址,是三个不同的变量。
使用 nonlocal 关键字!它可以修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量。将 global a 改成 nonlocal a,可以看到引用了 outer 函数的 a 变量。
例 3:不要上机测试,请说出下面代码的运行结果:
a = 10
def test():
a += 1
print(a)
test()
a += 1 相当于 a = a + 1,按照赋值运算符的规则是先计算右边的 a + 1。但是,Python的规则是,如果在函数内部要修改一个变量,那么这个变量需要是内部变量,除非你用 global 声明了它是外部变量。很明显,我们没有在函数内部定义变量 a,所以会弹出局部变量在未定义之前就引用的错误。
UnboundLocalError: local variable ‘a’ referenced before assignment
例 4(要注意其中的闭包,也就是函数内部封装了函数):
name = 'jack'
def outer():
name='tom'
def inner():
name ='mary'
print(name)
inner()
outer()
因为 inner 函数本身有name变量,所以打印结果是 mary。
例 5:
name ='jack'
def f1():
print(name)
def f2():
name = 'eric'
f1()
f2()
这题有点迷惑性,应该是 ‘eric’ 吧,因为 f2 函数调用的时候,在内部又调用了 f1 函数,f1 自己没有 name 变量,那么就往外找,发现 f2 定义了个 name,于是就打印这个 name。错了!!!结果是 ‘jack’!
Python 函数的作用域取决于其函数代码块在整体代码中的位置,而不是调用时的位置。调用 f1 的时候,会去 f1 函数的定义体查找,对于 f1 函数,它的外部是 name =‘jack’,而不是 name = ‘eric’。
例 6,f2 函数返回了 f1 函数:
name = 'jack'
def f2():
name = 'eric'
return f1
def f1():
print(name)
ret = f2() # 和 ret = f2 不同,前者返回 f2 函数结果,后者为 f2 函数的引用。
ret()
注意:
f2() # <function f1 at ****>
f2()() # jack
仔细回想前面的例子,其实这里有异曲同工之妙,所以结果还是 ‘jack’。
总结:
1、4个作用域从内往外找,内部一旦引用就不能再次声明或赋值。
2、各是各的作用域,除非声明 global 或闭包函数内声明 nonlocal。
# 全局变量 global
a = 12
b = [1,2]
print(a)
def f1():
a=4 # local 和外部的 a 没有任何关系,也就是指向一个不同的对象,只是同名。
c=3 # local
print(a, c)
def f2():
print(a) # 访问的是 global 只能访问不能修改
# a = 12 # UnboundLocalError: local variable 'a' referenced before assignment
def f3():
global a # 可操作全局变量 a
print(a)
a=10
print(a)
def f4(a):
# a 作为参数
print(a)
a=14
print(a)
f1(),f2(),f3(),f4(a)
print(a)
# 函数内部 如果声明外部变量为全局变量,则可操作外部变量;否则只能访问不能改变地址。
# 同名变量也可以作为局部变量,但前题是之前没有被引用。
# 函数内部:变量地址一旦变化,就变成局部变量。以前的一切引用都是Undefined variable。
def func():
# b += [3]
# UnboundLocalError 此处等价于 b = b + [3] 前者不改变地址,后者会改变,前题是先有 b,注意在函数外部是不等价的,外部 b += [3]不会改变地址。
b.append(3) # 此处访问的是 global
c = 3 # 局部变量 local
print(a, b, c)
func()
print(a, b)
# print(c) 外部不能访问内部的变量