Python函数中的变量作用域详解
Python 变量作用域
目录
- 变量作用域的基本概念
- 局部变量 (Local Variables)
- 全局变量 (Global Variables)
- global 关键字
- 嵌套函数 (Nested Functions)
- nonlocal 关键字
- LEGB 规则
- 作用域冲突与优先级
- 内置函数冲突
- 最佳实践
变量作用域的基本概念
在 Python 中,变量作用域指的是变量在程序中可被访问的范围。了解作用域对于编写可维护、无 bug 的代码至关重要。Python 中主要有四种作用域:
- 局部作用域 (Local) – 函数内部定义的变量
- 嵌套作用域 (Enclosing) – 外部函数中定义的变量(对于嵌套函数)
- 全局作用域 (Global) – 模块级别定义的变量
- 内置作用域 (Built-in) – Python 内置的名称
局部变量 (Local Variables)
局部变量是在函数内部定义的变量,它们只能在该函数内部访问。
def my_function():
x = 10 # 局部变量
print(f"局部变量 x 的值是: {x}")
my_function() # 输出: 局部变量 x 的值是: 10
# 尝试在函数外部访问局部变量会导致错误
try:
print(x)
except NameError as e:
print(f"错误: {e}") # 输出: 错误: name 'x' is not defined
局部变量的特点:
def func1():
y = 20
print(f"func1 中的 y: {y}")
def func2():
y = 30
print(f"func2 中的 y: {y}")
func1() # 输出: func1 中的 y: 20
func2() # 输出: func2 中的 y: 30
全局变量 (Global Variables)
全局变量是在函数外部(模块级别)定义的变量,可以在整个模块中访问。
# 全局变量
global_var = 100
def print_global():
print(f"函数内部访问全局变量: {global_var}")
print(f"全局作用域中的全局变量: {global_var}") # 输出: 全局作用域中的全局变量: 100
print_global() # 输出: 函数内部访问全局变量: 100
全局变量的特点:
global
关键字)counter = 0
def increment():
# 尝试直接修改全局变量(这将创建新的局部变量)
counter = counter + 1 # UnboundLocalError
print(f"计数器: {counter}")
try:
increment()
except UnboundLocalError as e:
print(f"错误: {e}")
# 输出: 错误: local variable 'counter' referenced before assignment
global 关键字
global
关键字用于在函数内部声明一个变量是全局变量,允许我们在函数内部修改全局变量。
counter = 0
def increment():
global counter # 声明 counter 是全局变量
counter = counter + 1
print(f"计数器: {counter}")
increment() # 输出: 计数器: 1
increment() # 输出: 计数器: 2
print(f"全局计数器: {counter}") # 输出: 全局计数器: 2
使用 global 的注意事项:
- 如果只需要读取全局变量,不需要使用
global
关键字 - 使用
global
后,该名称将在整个函数中被视为全局变量 global
声明必须在变量使用前出现
x = 5
def example():
# 只读取全局变量,不需要 global
print(f"全局 x 的值: {x}") # 输出: 全局 x 的值: 5
def bad_example():
print(x) # 这会引发错误,因为后面 x 被当作局部变量
x = 10 # 这定义了一个局部变量 x
def good_example():
global x # 声明 x 是全局变量
print(f"修改前 x 的值: {x}") # 输出: 修改前 x 的值: 5
x = 10 # 修改全局变量
print(f"修改后 x 的值: {x}") # 输出: 修改后 x 的值: 10
example()
try:
bad_example()
except UnboundLocalError as e:
print(f"错误: {e}")
# 输出: 错误: local variable 'x' referenced before assignment
good_example()
print(f"函数外部 x 的值: {x}") # 输出: 函数外部 x 的值: 10
嵌套函数 (Nested Functions)
嵌套函数是在另一个函数内部定义的函数。在 Python 中,嵌套函数可以访问其外部函数中定义的变量。
def outer_function():
outer_var = "I'm in the outer function"
def inner_function():
print(outer_var) # 可以访问外部函数的变量
inner_function() # 输出: I'm in the outer function
outer_function()
嵌套函数的特点:
nonlocal
关键字允许内部函数修改外部函数中定义的变量def counter_function():
count = 0
def increment():
# 尝试修改外部函数的变量
count = count + 1 # UnboundLocalError
return count
return increment
try:
counter = counter_function()
print(counter())
except UnboundLocalError as e:
print(f"错误: {e}")
# 输出: 错误: local variable 'count' referenced before assignment
nonlocal 关键字
nonlocal
关键字用于在嵌套函数中声明一个变量来自于外部函数的作用域,允许内部函数修改外部函数中定义的变量。
def counter_function():
count = 0
def increment():
nonlocal count # 声明 count 是从外部函数作用域获取的
count = count + 1
return count
return increment
counter = counter_function()
print(counter()) # 输出: 1
print(counter()) # 输出: 2
print(counter()) # 输出: 3
使用 nonlocal 的注意事项:
nonlocal
只能引用外部函数的变量,不能引用全局变量- 被引用的变量必须存在于某个外部函数的作用域中
nonlocal
声明必须在变量使用前出现
def outer():
x = "outer"
def inner1():
nonlocal x
x = "modified by inner1"
def inner2():
x = "local to inner2" # 创建局部变量,不影响外部
print(f"开始时 x = {x}") # 输出: 开始时 x = outer
inner1()
print(f"调用 inner1 后 x = {x}") # 输出: 调用 inner1 后 x = modified by inner1
inner2()
print(f"调用 inner2 后 x = {x}") # 输出: 调用 inner2 后 x = modified by inner1
outer()
LEGB 规则
Python 在解析变量名时遵循 LEGB 规则,按以下顺序查找变量:
- Local(局部)- 函数内部
- Enclosing(嵌套)- 外部函数中
- Global(全局)- 模块级别
- Built-in(内置)- Python的内置命名空间
这个规则决定了在访问变量时 Python 如何确定使用哪个变量。
x = "全局变量" # Global
def outer():
x = "外部函数变量" # Enclosing
def inner():
x = "局部变量" # Local
print(f"inner: {x}")
inner() # 输出: inner: 局部变量
print(f"outer: {x}")
outer() # 输出: outer: 外部函数变量
print(f"global: {x}") # 输出: global: 全局变量
下面的示例,展示了 LEGB 规则:
# Built-in
# len 是内置函数
# Global
x = "global x"
def outer():
# Enclosing
x = "enclosing x"
def inner():
# Local
# x = "local x" # 如果取消注释这行,会打印 "local x"
print(f"x: {x}")
inner()
# 根据LEGB规则
# 如果inner函数中有局部变量x,使用local x
# 否则,如果outer函数中有变量x,使用enclosing x
# 否则,如果全局作用域有变量x,使用global x
# 否则,查找内置作用域
outer() # 输出: x: enclosing x
作用域冲突与优先级
当不同作用域中存在同名变量时,Python 按照 LEGB 规则确定优先级:
- 局部变量优先于外部函数变量
- 外部函数变量优先于全局变量
- 全局变量优先于内置名称
局部作用域与全局作用域的冲突
value = 100 # 全局变量
def test_scope():
value = 200 # 局部变量
print(f"函数内部 value: {value}") # 使用局部变量
test_scope() # 输出: 函数内部 value: 200
print(f"函数外部 value: {value}") # 输出: 函数外部 value: 100
使用 global
可以改变这一默认行为:
value = 100 # 全局变量
def test_global():
global value # 声明使用全局变量
print(f"修改前 value: {value}") # 输出: 修改前 value: 100
value = 200 # 修改全局变量
print(f"修改后 value: {value}") # 输出: 修改后 value: 200
test_global()
print(f"函数调用后 value: {value}") # 输出: 函数调用后 value: 200
局部作用域与外部函数作用域的冲突
def outer_func():
value = 100 # 外部函数变量
def inner_func():
value = 200 # 局部变量
print(f"内部函数 value: {value}") # 使用局部变量
inner_func() # 输出: 内部函数 value: 200
print(f"外部函数 value: {value}") # 输出: 外部函数 value: 100
outer_func()
使用 nonlocal
可以改变这一默认行为:
def outer_func():
value = 100 # 外部函数变量
def inner_func():
nonlocal value # 声明使用外部函数变量
print(f"修改前 value: {value}") # 输出: 修改前 value: 100
value = 200 # 修改外部函数变量
print(f"修改后 value: {value}") # 输出: 修改后 value: 200
inner_func()
print(f"外部函数 value: {value}") # 输出: 外部函数 value: 200
outer_func()
多层嵌套函数中的作用域
def level1():
x = 1
def level2():
x = 2
def level3():
nonlocal x # 这会引用 level2 中的 x
x = 3
print(f"level3 x: {x}")
level3() # 输出: level3 x: 3
print(f"level2 x: {x}") # 输出: level2 x: 3(被 level3 修改)
level2()
print(f"level1 x: {x}") # 输出: level1 x: 1(不受影响)
level1()
内置函数冲突
当我们创建与 Python 内置函数同名的变量时,会覆盖内置函数。这是 LEGB 规则中"B"(Built-in)的体现:内置名称的优先级最低。
# 正常使用内置函数 len
print(len([1, 2, 3])) # 输出: 3
# 创建与内置函数同名的变量
len = "这不再是一个函数"
try:
print(len([1, 2, 3])) # TypeError: 'str' object is not callable
except TypeError as e:
print(f"错误: {e}")
# 输出: 错误: 'str' object is not callable
# 恢复内置函数
del len
print(len([1, 2, 3])) # 输出: 3
其他常见的被覆盖的内置函数示例:
# 覆盖 max 函数
max = 100
try:
largest = max(1, 2, 3) # TypeError
except TypeError as e:
print(f"错误: {e}")
# 输出: 错误: 'int' object is not callable
# 覆盖 list 函数
list = [1, 2, 3]
try:
new_list = list("abc") # TypeError
except TypeError as e:
print(f"错误: {e}")
# 输出: 错误: 'list' object is not callable
警告:不要使用与内置函数同名的变量名,这会导致内置函数无法使用,并可能引发难以调试的错误。
最佳实践
-
避免使用全局变量
- 全局变量使代码难以理解和维护
- 尽量通过参数传递和返回值来共享数据
-
减少使用
global
和nonlocal
关键字 - 过度使用会使代码难以追踪和理解
- 考虑使用类来管理状态,而不是依赖嵌套函数和变量
-
避免使用与内置函数同名的变量
- 避免使用
list
、dict
、sum
、max
等作为变量名 - 可以使用类似
list_
、dict_
的命名方式,或更具描述性的名称 -
利用闭包特性
- 嵌套函数可以记住并访问其外部作用域中的变量,这称为闭包
- 闭包可以用于创建拥有私有状态的函数
def create_counter(start=0):
"""创建一个计数器函数"""
def counter():
nonlocal start
start += 1
return start
return counter
# 使用闭包创建两个独立的计数器
counter1 = create_counter(10)
counter2 = create_counter(100)
print(counter1()) # 11
print(counter1()) # 12
print(counter2()) # 101
print(counter1()) # 13
- 在复杂情况下使用类替代嵌套函数
class Counter:
def __init__(self, start=0):
self.value = start
def increment(self):
self.value += 1
return self.value
def reset(self):
self.value = 0
return self
# 使用类创建计数器
counter1 = Counter(10)
counter2 = Counter(100)
print(counter1.increment()) # 11
print(counter1.increment()) # 12
print(counter2.increment()) # 101
总结
- 局部变量在函数内部定义,只在函数内部可见
- 全局变量在模块级别定义,可以在整个模块中访问
- global 关键字允许在函数内部修改全局变量
- 嵌套函数可以访问外部函数中定义的变量
- nonlocal 关键字允许在嵌套函数中修改外部函数中定义的变量
- LEGB 规则定义了 Python 查找变量的顺序:Local → Enclosing → Global → Built-in
- 作用域冲突时,优先级按 LEGB 顺序递减,可以使用
global
和nonlocal
改变默认行为 - 定义与内置函数同名的变量会覆盖内置函数,应当避免
作者:想知道哇