第37集:变量作用域

学习目标

  • 理解变量作用域的概念
  • 掌握局部变量和全局变量的区别
  • 学会使用global和nonlocal关键字
  • 理解LEGB查找规则
  • 掌握嵌套函数中的变量作用域

一、什么是变量作用域?

作用域是指变量在程序中的可访问范围。

作用域类型

类型 说明 访问范围
局部作用域 在函数内部定义的变量 仅在函数内部
全局作用域 在函数外部定义的变量 整个程序

生活类比

  • 局部变量:房间内的物品,只有房间里的人能用
  • 全局变量:公共区域的物品,所有人都能用
# 全局变量 - 公共区域
public_water = "公共饮水机"

def room1():
    # 局部变量 - 房间内
    private_cup = "房间1的杯子"
    print(f"可以使用:{public_water}")
    print(f"可以使用:{private_cup}")

def room2():
    # 局部变量 - 房间内
    private_cup = "房间2的杯子"
    print(f"可以使用:{public_water}")
    print(f"可以使用:{private_cup}")
    # print(private_cup)  # 不能使用房间1的杯子

二、局部变量

什么是局部变量?

局部变量是在函数内部定义的变量,只能在该函数内部访问。

示例1:局部变量基本用法

def test_function():
    """演示局部变量"""
    local_var = "我是局部变量"  # 局部变量
    print(f"函数内部:{local_var}")

test_function()
# print(local_var)  # 报错:局部变量在函数外部无法访问

示例2:局部变量的生命周期

def demo1():
    """演示局部变量的生命周期"""
    x = 10  # 函数开始时创建
    print(f"1. x = {x}")
    x = 20  # 修改局部变量
    print(f"2. x = {x}")
    # 函数结束时,x被销毁

demo1()
# print(x)  # 报错:x已经不存在

示例3:多个函数的同名局部变量

def func1():
    """函数1"""
    x = 10
    print(f"func1中的x = {x}")

def func2():
    """函数2"""
    x = 20  # 这是另一个x,与func1中的x无关
    print(f"func2中的x = {x}")

func1()  # 输出:func1中的x = 10
func2()  # 输出:func2中的x = 20
# 两个x互不影响

示例4:局部变量与参数

def calculate(a, b):
    """计算函数"""
    # a和b也是局部变量(形式参数)
    result = a + b  # result是局部变量
    return result

# 函数调用后,a、b、result都会被销毁
sum_value = calculate(5, 3)
print(sum_value)  # 输出:8

三、全局变量

什么是全局变量?

全局变量是在函数外部定义的变量,可以在整个程序中访问。

示例5:全局变量基本用法

# 全局变量
global_var = 100

def test_function():
    """演示全局变量"""
    print(f"函数内部访问全局变量:{global_var}")

test_function()  # 输出:100
print(f"函数外部访问全局变量:{global_var}")  # 输出:100

示例6:全局变量与局部变量的优先级

x = 100  # 全局变量

def test():
    """演示局部变量和全局变量的优先级"""
    x = 200  # 局部变量(会遮蔽全局变量)
    print(f"函数内部:x = {x}")

test()  # 输出:200
print(f"函数外部:x = {x}")  # 输出:100

示例7:只读取全局变量

# 全局变量
count = 0

def increment():
    """增加计数器"""
    # 只读取全局变量,不需要global关键字
    new_count = count + 1
    print(f"当前计数:{count},新计数:{new_count}")
    # 注意:这不会修改全局变量count

print(f"初始count = {count}")  # 输出:0
increment()  # 输出:当前计数:0,新计数:1
print(f"调用后count = {count}")  # 输出:0(未改变)

四、global关键字

什么时候使用global?

当需要在函数内部修改全局变量时,必须使用global关键字。

示例8:使用global修改全局变量

count = 0  # 全局变量

def increment():
    """增加计数器"""
    global count  # 声明使用全局变量
    count = count + 1  # 修改全局变量
    print(f"计数器增加到:{count}")

print(f"初始count = {count}")  # 输出:0
increment()  # 输出:计数器增加到:1
print(f"调用后count = {count}")  # 输出:1
increment()  # 输出:计数器增加到:2
print(f"调用后count = {count}")  # 输出:2

示例9:不使用global的错误

# ❌ 错误示例
total = 100

def add_to_total():
    """添加到总计"""
    total = total + 50  # 报错:UnboundLocalError
    # 这里的total被当作局部变量,但使用时还未定义

# add_to_total()  # 报错

# ✅ 正确示例
total = 100

def add_to_total():
    """添加到总计"""
    global total  # 声明使用全局变量
    total = total + 50  # 正确修改全局变量

add_to_total()
print(total)  # 输出:150

示例10:global在多个函数中使用

# 全局变量
balance = 0

def deposit(amount):
    """存款"""
    global balance
    balance += amount
    print(f"存款¥{amount},余额:¥{balance}")

def withdraw(amount):
    """取款"""
    global balance
    if balance >= amount:
        balance -= amount
        print(f"取款¥{amount},余额:¥{balance}")
    else:
        print(f"余额不足,无法取款¥{amount}")

def get_balance():
    """查询余额"""
    global balance
    return balance

# 使用示例
deposit(1000)  # 输出:存款¥1000,余额:¥1000
withdraw(300)  # 输出:取款¥300,余额:¥700
withdraw(800)  # 输出:余额不足,无法取款¥800
print(f"当前余额:¥{get_balance()}")  # 输出:当前余额:¥700

五、嵌套函数和nonlocal关键字

什么是嵌套函数?

嵌套函数是在函数内部定义的函数。

示例11:嵌套函数

def outer():
    """外层函数"""
    outer_var = "外层变量"
    
    def inner():
        """内层函数"""
        inner_var = "内层变量"
        print(f"内层函数访问外层变量:{outer_var}")
        print(f"内层函数访问内层变量:{inner_var}")
    
    inner()
    print(f"外层函数访问外层变量:{outer_var}")
    # print(inner_var)  # 报错:无法访问内层变量

outer()

示例12:nonlocal关键字

nonlocal关键字用于在嵌套函数中修改外层函数的变量。

def outer():
    """外层函数"""
    counter = 0  # 外层函数的变量
    
    def inner():
        """内层函数"""
        nonlocal counter  # 声明使用外层函数的变量
        counter += 1
        print(f"计数器:{counter}")
    
    inner()  # 输出:计数器:1
    inner()  # 输出:计数器:2
    print(f"最终计数:{counter}")  # 输出:最终计数:2

outer()

示例13:多层嵌套

def level1():
    """第一层"""
    x = 1
    
    def level2():
        """第二层"""
        x = 2  # 创建新的局部变量
        
        def level3():
            """第三层"""
            nonlocal x  # 使用level2的x
            x += 1
            print(f"level3中的x = {x}")
        
        level3()  # 输出:level3中的x = 3
        print(f"level2中的x = {x}")  # 输出:level2中的x = 3
    
    level2()
    print(f"level1中的x = {x}")  # 输出:level1中的x = 1

level1()

六、LEGB查找规则

什么是LEGB?

Python查找变量的顺序遵循LEGB规则:

作用域 说明 示例
L (Local) 局部作用域 函数内部的变量
E (Enclosing) 嵌套作用域 外层函数的变量
G (Global) 全局作用域 模块级别的变量
B (Built-in) 内置作用域 Python内置的变量和函数

示例14:LEGB规则演示

# G: 全局作用域
x = "全局变量"

def outer():
    # E: 嵌套作用域(外层函数)
    x = "外层函数变量"
    
    def inner():
        # L: 局部作用域(内层函数)
        x = "局部变量"
        print(f"L: {x}")
        
    inner()
    print(f"E: {x}")

outer()
print(f"G: {x}")

# 输出:
# L: 局部变量
# E: 外层函数变量
# G: 全局变量

示例15:不同层级访问

# 全局变量
built_in_func = print  # B: 内置作用域
global_var = "全局"     # G: 全局作用域

def outer():
    """外层函数"""
    enclosing_var = "外层"  # E: 嵌套作用域
    
    def inner():
        """内层函数"""
        local_var = "局部"  # L: 局部作用域
        
        print(f"L: {local_var}")          # 输出:局部
        print(f"E: {enclosing_var}")      # 输出:外层
        print(f"G: {global_var}")         # 输出:全局
        print(f"B: {built_in_func}")      # 输出:<built-in function print>
    
    inner()

outer()

示例16:遮蔽效应

# 全局变量
x = "全局x"

def outer():
    """外层函数"""
    x = "外层x"
    
    def inner():
        """内层函数"""
        x = "局部x"  # 遮蔽外层x和全局x
        print(f"inner中的x = {x}")
    
    inner()
    print(f"outer中的x = {x}")

outer()
print(f"全局的x = {x}")

# 输出:
# inner中的x = 局部x
# outer中的x = 外层x
# 全局的x = 全局x

七、实际应用案例

案例1:计数器

def make_counter():
    """创建计数器"""
    count = 0  # 外层函数的变量
    
    def counter():
        """计数器函数"""
        nonlocal count  # 使用外层函数的变量
        count += 1
        return count
    
    return counter

# 创建两个独立的计数器
counter1 = make_counter()
counter2 = make_counter()

print(f"counter1: {counter1()}")  # 输出:1
print(f"counter1: {counter1()}")  # 输出:2
print(f"counter2: {counter2()}")  # 输出:1
print(f"counter1: {counter1()}")  # 输出:3
print(f"counter2: {counter2()}")  # 输出:2

案例2:累加器

def make_accumulator(initial=0):
    """创建累加器"""
    total = initial
    
    def add(value):
        """累加函数"""
        nonlocal total
        total += value
        return total
    
    def get_total():
        """获取总和"""
        nonlocal total
        return total
    
    return add, get_total

# 创建累加器
add_func, get_func = make_accumulator(100)

print(f"初始值:{get_func()}")  # 输出:100
print(f"累加50:{add_func(50)}")  # 输出:150
print(f"累加30:{add_func(30)}")  # 输出:180
print(f"总和:{get_func()}")  # 输出:180

案例3:配置管理器

# 全局配置
config = {
    "debug": False,
    "language": "zh",
    "theme": "light"
}

def get_config(key):
    """获取配置"""
    return config.get(key)

def set_config(key, value):
    """设置配置"""
    global config
    config[key] = value

def show_config():
    """显示配置"""
    global config
    print("当前配置:")
    for key, value in config.items():
        print(f"  {key}: {value}")

# 使用示例
show_config()
set_config("debug", True)
set_config("theme", "dark")
show_config()

案例4:游戏状态管理

# 游戏全局状态
game_state = {
    "score": 0,
    "lives": 3,
    "level": 1
}

def add_score(points):
    """添加分数"""
    global game_state
    game_state["score"] += points
    print(f"获得{points}分!")

def lose_life():
    """失去生命"""
    global game_state
    game_state["lives"] -= 1
    if game_state["lives"] <= 0:
        print("游戏结束!")
    else:
        print(f"剩余生命:{game_state['lives']}")

def next_level():
    """进入下一关"""
    global game_state
    game_state["level"] += 1
    print(f"进入第{game_state['level']}关!")

def show_status():
    """显示状态"""
    global game_state
    print("\n游戏状态:")
    for key, value in game_state.items():
        print(f"  {key}: {value}")

# 游戏示例
show_status()
add_score(100)
add_score(50)
lose_life()
next_level()
show_status()

八、最佳实践

实践1:避免滥用全局变量

# ❌ 不好的做法:使用全局变量
count = 0

def increment():
    global count
    count += 1

# ✅ 好的做法:使用类或函数返回值
class Counter:
    def __init__(self):
        self.count = 0
    
    def increment(self):
        self.count += 1

# 或使用闭包
def make_counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

实践2:合理使用局部变量

# ✅ 好的做法:使用局部变量
def calculate(numbers):
    """计算列表中的偶数和"""
    total = 0  # 局部变量
    for num in numbers:
        if num % 2 == 0:
            total += num
    return total

实践3:明确使用global和nonlocal

# ✅ 明确声明global
def update_global():
    global config
    config["version"] = "2.0"

# ✅ 明确声明nonlocal
def outer():
    value = 0
    def inner():
        nonlocal value
        value += 1
    inner()

实践4:函数参数优于全局变量

# ❌ 不好的做法
discount_rate = 0.1

def calculate_price(price):
    return price * (1 - discount_rate)

# ✅ 好的做法
def calculate_price(price, discount_rate):
    return price * (1 - discount_rate)

# 使用
final_price = calculate_price(100, 0.1)

九、常见错误与调试

错误1:未声明global就修改全局变量

# ❌ 错误
x = 10

def modify():
    x = 20  # 创建了局部变量,不是修改全局变量

modify()
print(x)  # 输出:10(未修改)

# ✅ 正确
x = 10

def modify():
    global x
    x = 20

modify()
print(x)  # 输出:20

错误2:混淆local和nonlocal

x = 100  # 全局变量

def outer():
    x = 10  # 外层函数的变量
    
    def inner():
        # ❌ 错误:nonlocal不能用于全局变量
        # nonlocal x  # 报错
        
        # ✅ 正确:使用global
        global x
        x = 200

错误3:在嵌套函数中错误使用global

# ❌ 错误
def outer():
    x = 10
    
    def inner():
        global x  # 错误:x不是全局变量
        x = 20

# ✅ 正确
def outer():
    x = 10
    
    def inner():
        nonlocal x  # 正确:使用nonlocal
        x = 20

调试技巧:打印变量位置

def debug_function():
    """调试变量位置"""
    global_var = "全局"
    
    def inner():
        local_var = "局部"
        print(f"局部变量local_var: {local_var}")
        print(f"全局变量global_var: {global_var}")
    
    inner()
    # print(local_var)  # 报错:无法访问局部变量
    print(f"全局变量global_var: {global_var}")

debug_function()

十、小结

知识点 说明
局部变量 函数内部定义,仅函数内可用
全局变量 函数外部定义,整个程序可用
global 声明使用全局变量,可修改全局变量
nonlocal 声明使用外层函数的变量,可修改外层变量
LEGB规则 Local → Enclosing → Global → Built-in
优先级 局部变量优先于全局变量

十一、课后练习

练习1

定义函数increment_counter(),使用全局变量实现计数器功能。

练习2

定义函数make_multiplier(factor),使用闭包创建乘法器。

练习3

定义函数update_config(key, value),使用global修改全局配置字典。

练习4

定义嵌套函数outer()inner(),演示nonlocal的使用。

练习5

创建一个游戏状态管理器,使用全局变量管理分数、生命值等状态。

« 上一篇 返回值与return语句 下一篇 » lambda表达式