Python中的作用域(Scope)和命名空间(Namespace)
前言
闭包有很多高级应用,比如装饰器(decorators),但闭包
这个概念有点难以理解,因为它涉及作用域和命名空间。
为了更好的理解闭包,先看看什么是作用域
、命名空间
。
作用域(Scope)
作用域是 Python 中某一个变量名字可以被识别的区域。在任何时刻,都有三个或四个嵌套的作用域层级:
- 全局作用域(Global Scope):当前脚本的最外层。
- 局部作用域(Local Scope):在函数或方法内,在函数内部定义了一个变量,它就属于这个作用域内。
- 封闭作用域(Enclosing Scope):嵌套函数中,在当前函数外部,但又不属于全局的作用域。
- 内置作用域(Built-in Scope): Python 内置对象的作用域(如
len
,str
等)。
当你在函数内部查找变量时,Python 会按照以下顺序在作用域中进行搜索:
- 局部作用域
- 封闭作用域
- 全局作用域
- 内置作用域
命名空间(Namespace)
在 Python 中,命名空间是一个保存了变量名和它们对应对象的地方,每个变量名都映射到一个对象。
命名空间分为以下几个层级:
- 局部命名空间(Local Namespace):函数内部的命名空间。
- 全局变量空间(Global Namespace):模块的命名空间,包含导入的模块、函数定义等。
- 内置命名空间(Built-in Namespace):Python 内置对象的命名空间,如
print
,len
等。
当你在函数内部声明变量时,这些变量位于局部命名空间
。函数外部定义的变量位于全局命名空间
。
作用域与命名空间的关系
每当你访问一个变量,Python 会遵循LEGB规则(Local, Enclosing, Global, Built-in)按顺序在不同的层级中,查找这个变量名对应的对象。这些不同层级就是作用域
,而每个作用域
对应着一个命名空间
,命名空间
可以看作是一个存储变量名与对应对象映射关系的容器。
通过一个简单的例子来逐一理解这些概念:
# 这里定义了一个全局变量,它位于全局命名空间(Global Namespace)
global_var = "我是一个全局变量"
# len 是 Python 的内置函数,位于内置命名空间(Built-in Namespace)
# 我们会在后面覆盖它来展示内置作用域
def outer_function():
# 这个变量位于 outer_function 的局部命名空间(Local Namespace)
# 对于内部的 inner_function 来说,这个命名空间是它的封闭命名空间(Enclosing Namespace)
enclosed_var = "我是一个封闭变量"
def inner_function():
# 这个变量位于 inner_function 的局部命名空间(Local Namespace)
local_var = "我是一个局部变量"
print(local_var) # 访问局部变量
print(enclosed_var) # 访问封闭作用域中的变量
print(global_var) # 访问全局变量
print(len([])) # 访问内置作用域的函数
# 调用内部函数
inner_function()
# 覆盖内置的 len 函数,这将在全局命名空间中创建一个新的名字 len
len = "这是覆盖内置len函数的全局变量"
# 调用外部函数
outer_function()
lobal_var
定义在最外层,属于全局作用域(Global Scope),位于全局命名空间(Global Namespace)。outer_function
定义了一个名为enclosed_var
的变量,这个变量是函数outer_function
的局部作用域(Local Scope),对于inner_function
来说,这个作用域是封闭作用域(Enclosing Scope),变量位于封闭的局部命名空间中。local_var
是inner_function
的局部变量,属于局部作用域(Local Scope),位于它的局部命名空间(Local Namespace)。len
是一个内置函数,它原本是内置作用域(Built-in Scope)的一部分,位于内置命名空间(Built-in Namespace)。但在这个例子中,我们在全局命名空间中又定义了一个同名的字符串变量len
,这会覆盖内置的len
函数。
上面代码执行结果如下:
我是一个局部变量
我是一个封闭变量
我是一个全局变量
这是覆盖内置len函数的全局变量
你肯定会问,为什么上面描述命名空间时,没有提到封闭命名空间
(Enclosing Namespace),这是因为在 Python 中,封闭命名空间
这个术语通常不会单独使用,而是在讨论闭包和作用域的上下文中才会出现。
这个术语并不是官方文档中的标准术语,但可以被用来指代嵌套函数中外层函数的局部变量的集合,从而帮助更好的理解闭包是如何工作的。
封闭命名空间(Enclosing Namespace)的概念是相对于内部函数来说的。
在上面的例子中:
对于 outer_function
来说,变量 enclosed_var
位于它的局部命名空间(Local Namespace),但是对于 inner_function
来说,变量 enclosed_var
位于它的封闭命名空间(Enclosing Namespace)。
这种封闭命名空间的概念是闭包的基础,它允许内部函数访问外部函数的局部变量,即便外部函数的执行已经结束,这种结构使得内部函数可以保持外部函数状态的引用。
闭包与作用域的关系
闭包
即一个函数内部定义了另一个函数,并且这个内部函数还引用了外部函数的变量。通常当外部函数执行完成后,其局部作用域的生命周期就结束了,局部变量也会被垃圾回收。
但是,如果内部函数继续存活,比如被作为返回值传递出去,那么这个内部函数仍然持有外部函数的变量引用。这些变量的生命周期会被延长,仍然可以在外部函数执行完毕后被内部函数访问,这就是闭包。
闭包的强大之处在于,即使外部函数的执行上下文已经不存在了,闭包依然保留了外部函数作用域中变量的状态。这就允许我们在 Python 中实现某些编程模式,比如工厂函数、装饰器等。