在Python中,所有所谓的变量,其实都是名字,这些名字指向一个或多个Python对象。
比如以下代码:
a=1
b=a
c='china'
id(a)==id(b)
True
id(a)==id(c)
False
从中可以看出,名字 a
和 b
指向同一个Python对象,即一个 int
类型的对象,
这个对象的值为 1
;而 c
则指向另一个Python对象,它是一个 str
类型的对象。
所有的这些名字,都存在于一个表里(又称为命名空间),一般情况下,称之为局部变量( locals
),可以通过 locals()
函数调用看到。
a in locals(), c in locals()
(False, False)
现在是直接在Python shell
中执行这一些代码,实际上这些变量也是全局的,
所以在一个叫 globals
的表里也可以看到。
a in globals() , c in globals()
(False, False)
如果在一个函数里面定义这些变量,情况会有所不同。
def foo(x):
e = 1
f = e
g ='china'
print('*'*10)
print(e in globals(), f in globals())
print('*'*10)
print(c) # 打印全局变量c
foo(1)
********** False False ********** china
可以看到函数中的 locals()
返回值并不包含之前定义在全局中的a、b、c等名字,
只有定义在函数内的e、f、g和函数形参x,这是什么原因呢?要回答这个问题,首先要理解Python中变量的作用域。
变量作用域
Python中所有的变量名都是在赋值的时候生成的, 而对任何变量名的创建、査找或者改变都会在命名空间(namespace)中进行。 变量名所在的命名空间直接决定了其能访问到的范围,即变量的作用域。 Python中的作用域自Python2.2之后分为局部作用域(local)、全局作用域(Global)、 嵌套作用域(enclosing functions locals)以及内置作用域(Build-in)这4种。
- 局部作用域: 一般来说函数的每次调用都会创建一个新的本地作用域,拥有新的命名空间。
因此函数内的变量名可以与函数外的其他变量名相同,由于其命名空间不同, 并不会产生冲突。
默认情况下函数内部任意的赋值操作(包括
=
语句、import
语句、def
语句、参数传递等)所定义的变量名, 如果没用global
语句,则申明都为局部变量,即仅在该函数内可见。 - 全局作用域: 定义在Python模块文件中的变量名拥有全局作用域,需要注意的是这里的全局仅限单个文件, 即在一个文件的顶层的变量名仅在这个文件内可见,并非所有的文件, 其他文件中想使用这些变量名必须先导入文件对应的模块。 当在闲数之外给一个变量名赋值时是在其全局作用域的情况下进行的。
- 嵌套作用域: 一般在多重函数嵌套的情况下才会考虑到。需要注意的是
global
语句仅针对全局变量, 在嵌套作用域的情况下,如果想在嵌套的闲数内修改外层函数中定义的变量,即使使用global
进行申明也不能达到目的, 其结果最终是在嵌套的函数所在的命名空间中创建了一个新的变量。示例如下:
def ex2():
var = 'a'
def inner():
global var
var = 'b'
print('inside inner,var is',var)
inner()
print('inside outer function,var is',var)
ex2()
inside inner,var is b inside outer function,var is a
- 内置作用域: 这个相对简单,它是通过一个标准库中名为
__builtin__
的模块来实现的。
回到前面代码中的语句 printc
,仍然正确输出了 china
这个值。
这是因为当访问一个变量的时候,其査找顺序遵循变量解析机制LEGB法则,即依次搜索4个作用域:
局部作用域、嵌套作用域、全局作用域以及内置作用域,并在第一个找到的地方停止搜寻,
如果没有搜到,则会抛出异常。因此当存在多个间名变置的时候,操作生效的往往是搜索顺序在前的。
具体来说Python的名字査找机制如下:
1)在最内层的范围内查找,一般而言,就是函数内部,local()
里面査找。
2)在模块内查找,即在 globals()
里面查找。
3)在外层査找,即在内置模块中査找,也就是在 __builtin__
中査找。
至此,可以理解清楚能够在 foo()
函数中访问到名字C的原因在于当Python在局部变量中找不到c时,
它会尝试在模块级的全局变量中查找,并成功地找到该名字。
不过,当我们试图改变全局变量的值时,事情可能跟想象的稍有不同。
def bar():
c= 'america'
print(c)
bar()
america
print(c)
china
真奇怪!不是吗?在 bar()
函数中修改c的值,并没有修改到全局变量的c,
而是好像 bar()
函数有了一个局部变量c—样!事实上确实如此,在CPython的实现中,
只要出现了赋值语句(或者称为名字绑定),
那么这个名字就被当作局部变量来对待,所以在这里如果需要改变全局变量 c
的值,就需要使用 global
关键字。
def bar():
global c
c='america'
print(c)
bar()
america
print(c)
america
不过,随着更多Python特性的加入,事情变得更加复杂起来。 比如在Python闭包中,有这样的问题:
def foo():
a = 1
def bar():
b= a*2
a = b +1
print(a)
return bar
foo()
<function __main__.foo.<locals>.bar()>
从上例中可以看出,在闭包 bar()
中,在编译代码为字节码时,
因为存在 a=b+l
这条语句,所以a被当作了局部变量看待,
而执行时因为 b=a*2
先执行,此时局部变量 a
尚不存在, 所以产生了一个 UnboundLocalError
。
在Python2.x中可以使用 global
关键字解决部分问题, 先把a创建为一个模块全局变量,
然后在所有读写(包括只是访问)该变量的作用域中都要先使用 global
声明其为全局变量。
a = 1
def foo(x):
global a
a = a * x
def bar():
global a
b = a * 2
a = b+1
print(a)
return bar
foo(1)()
3
这种方案抛开编程语言并不提倡全局变量不谈,有的时候还影响业务逻辑。
此外,还有把 a
作为容器的一个元素来对待的方案,但也都相当复杂。
真正的解决方案是Python3引入 的 nonlocal
关键字,通过它能够解决这方面的问题。
def foo(x):
a = x
def bar():
nonlocal a
b= a *2
a = b+1
print(a)
return bar
bar1 = foo(1)