Python学习之命名空间和作用域
关于Python,我们前面的基础部分,基本也说完了,包括我们也讲了高阶特性,面向对象编程。现在我来补充一个知识:命名空间和作用域。
这是Python两个重要的概念,它们决定了变量的可见性和访问范围。理解命名空间和作用域可以帮助我们更好地编写代码,避免命名冲突,并提高代码的健壮性。
一、简介
首先,命名空间(Namespaces)就像是一个大字典,里面存着变量名和对应的东西(比如值或者函数)。这个字典告诉程序:“哥们,当我说这个名字的时候,我指的是这个东西!”但有趣的是,在不同的地方(也就是不同的命名空间)里,同一个名字可能会指向不同的东西。就像同名字的人在不同的城市,但是他们却是两个不一样的人!
然后,作用域(Scopes)就是规定了你在哪里能找到这些变量或者修改它们的值。它有点像是规定了一个变量的“活动范围”。在Python中,作用域告诉你:“哥们,这个地方,你可以用这个名字,改它的值也没问题!但那个地方,这个名字可就不认识了!”
🔊:所以,命名空间告诉你这个名字指的是啥,而作用域告诉你这个名字在哪里能用。
记住这两个概念,你就能理解Python程序中的变量是如何工作!
🌾 为什么命名空间和作用域对Python程序员重要?
- 避免命名冲突: 使用命名空间可以避免不同模块或库中的相同名称的变量或函数产生冲突。
- 代码组织和结构化: 通过命名空间,可以更好地组织代码,使代码结构更加清晰。
- 作用域规则: 了解作用域规则可以帮助程序员理解变量的生命周期和可见性,从而编写更加健壮的代码。
二、命名空间(Namespaces)
命名空间(Namespaces)是一个从名称到对象的映射,它允许程序员在代码中避免名称冲突。Python解释器会在多个不同的命名空间中管理这些名称。这些有助于确定变量的范围和可见性。
🌾 不同类型的命名空间
- 内置命名空间(Built-in Namespace):
- 包含了 Python 解释器启动时就加载到内存中的名称,这些名称对应着 Python 的内置函数和异常等。
- 例如,
print()
、len()
等函数都属于内置命名空间。
- 全局命名空间(Global Namespace):
- 全局命名空间是指在模块级别定义的名称空间,一般是指在函数外部定义的变量和函数名。
- 在一个模块中,全局命名空间始终有效,模块的全局变量和函数在整个模块中可见。
- 局部命名空间(Local Namespace):
- 每个函数调用会创建一个局部命名空间,用于存储该函数内部定义的变量名。
- 这些变量在函数执行期间有效,函数执行结束后,局部命名空间被销毁。
- 在函数内部定义的变量、函数参数以及由函数调用所创建的局部变量,都属于局部命名空间。
如下图:
示例代码:
# 全局命名空间 global_var = "I'm in the global namespace" def example_function(arg1): # 局部命名空间 local_var = "I'm in the local namespace" print(arg1) # 内置命名空间 print(len("hello")) # 内置函数 len() 属于内置命名空间
在这里:global_var
属于全局命名空间,example_function
和 local_var
属于 example_function
的局部命名空间。函数 len()
属于内置命名空间。
三、作用域(Scopes)
作用域(Scopes)是在程序中定义变量的区域,它决定了变量的可见性和访问规则。作用域规则确定了在何处可以访问变量以及如何解析变量名。
🌾 Python 的作用域规则按照以下顺序来查找变量:
- 局部作用域(Local Scope):
- 定义在函数内部的变量属于局部作用域。
- 在函数内部声明的变量只在该函数内部可见,函数执行结束后这些变量的作用域也就结束了。
-
嵌套作用域(Enclosing Scope):
- 嵌套作用域是指在函数内部定义的内部函数的作用域。
- 内部函数可以访问外部函数中的变量,但不能修改它们。
-
全局作用域(Global Scope):
- 全局作用域是指在模块级别定义的变量的作用域。
- 在模块中定义的变量可以在该模块中的任何地方使用。
-
内置作用域(Built-in Scope):
- 内置作用域包含了 Python 的内置函数和异常等。
- 这些名称在任何地方都可以访问到,无需导入任何模块。
上面这张图讲了作用域的四大规则,就是 LEGB 规则。下面接着看:
四、LEGB 规则
🌾 LEGB 规则的含义
LEGB 规则描述了 Python 中变量名解析的顺序,它按照如下顺序查找变量:
- L - Local(局部作用域)
- E - Enclosing(嵌套作用域)
- G - Global(全局作用域)
- B - Built-in(内置作用域)
当Python 在运行时遇到一个变量引用时,就会按照 LEGB 规则从局部作用域开始查找变量,然后逐级向上查找直到找到对应的变量或者查找完所有作用域。
🌾 LEGB 中的每个部分
🌾 简单来看一个示例代码
x = 'global' def outer(): x = 'outer' def inner(): x = 'inner' print(x) # 首先在局部作用域查找,输出 'inner' inner() outer() # 输出 'inner',因为 inner() 函数内部的 x 是局部变量 print(x) # 输出 'global',因为这里访问的是全局变量 x
在这里:inner()
函数首先在自己的局部作用域中查找变量 x
,找到了 'inner'
。而在最后一个 print(x)
中,因为没有在函数内部重新定义 x
,所以找到的是全局变量 x
,输出 'global'
。
五、示例和应用
🌾 示例 1:不同作用域下变量的可见性
# 全局作用域 global_var = "I'm in the global scope" def func(): # 局部作用域 local_var = "I'm in the local scope" print(global_var) # 可以访问全局变量 print(local_var) # 可以直接访问局部变量 func() print(global_var) # 可以在函数外部访问全局变量 # print(local_var) # 这里会报错,因为无法直接访问局部变量
🌾 示例 2:嵌套作用域
def outer(): outer_var = "I'm in the outer function" def inner(): inner_var = "I'm in the inner function" print(inner_var) # 可以直接访问内部函数的变量 print(outer_var) # 可以访问外部函数的变量 inner() # print(outer_var) # 这里会报错,因为无法直接访问外部函数的变量 outer()
六、一些常见问题与注意事项
在这里,我通过两个常见的问题,来看看一些注意事项。
🌾 未声明全局变量就在函数内修改:
x = 10 def func(): x += 5 # 会报错,因为 x 没有在函数内部声明为 global func()
这里局部变量冲突, 解决方法是在函数内部使用 global
关键字声明变量 x
为全局变量。
🌾 在嵌套函数中修改外部函数变量:
def outer(): count = 0 def inner(): count += 1 # 会报错,因为 count 没有在内部函数中声明为 nonlocal inner() print(count) outer()
这里的问题也一样,解决方法为:在内部函数中使用 nonlocal
关键字声明变量 count
为非局部变量。