函数作用域与闭包

Python 函数作用域、闭包和变量生命周期详解

分类: basics 难度: 中级 更新: 2024-01-01
作用域 闭包 global nonlocal 变量作用域

函数作用域与闭包

📝 概述

作用域是编程中的重要概念,它决定了变量在程序中的可见性和生命周期。Python 的作用域规则遵循 LEGB 原则(Local、Enclosing、Global、Built-in),理解作用域对于编写正确、高效的 Python 代码至关重要。闭包是函数式编程的重要特性,它允许内层函数访问外层函数的变量,创建强大而灵活的编程模式。

🎯 学习目标

  • 理解 Python 的作用域规则和 LEGB 原则
  • 掌握 global 和 nonlocal 关键字的使用
  • 学会创建和使用闭包
  • 了解变量的生命周期和销毁机制
  • 掌握默认参数的作用域特性
  • 能够解决作用域相关的常见问题

📋 前置知识

  • Python 函数定义和调用
  • 变量和数据类型
  • 可变对象和不可变对象的概念
  • 基本的面向对象概念

🔍 详细内容

作用域基础

作用域定义

作用域决定了变量在程序中的可见范围和访问权限。

## 全局作用域
global_var = "我是全局变量"

def outer_function():
#    # 外层函数作用域(Enclosing scope)
    outer_var = "我是外层函数变量"
    
    def inner_function():
#        # 局部作用域(Local scope)
        local_var = "我是局部变量"
        
#        # 可以访问所有外层作用域的变量
        print(f"局部变量:{local_var}")
        print(f"外层变量:{outer_var}")
        print(f"全局变量:{global_var}")
        print(f"内置函数:{len([1, 2, 3])}")
    
    inner_function()
    
#    # 无法访问内层函数的局部变量
#    # print(local_var)  # 这会引发 NameError

outer_function()

## 演示作用域查找顺序
def scope_demo():
    """演示 LEGB 作用域查找顺序"""
#    # Local - 局部作用域
    name = "局部作用域"
    
    def inner():
#        # 如果这里不定义 name,会向上查找
        print(f"内层函数访问:{name}")
    
    inner()
    print(f"外层函数访问:{name}")

scope_demo()

变量查找规则

Python 按照 LEGB 顺序查找变量:

## Built-in scope(内置作用域)
## len, print, str 等内置函数

## Global scope(全局作用域)
len = "我重新定义了 len"  # 不推荐这样做

def demonstrate_legb():
#    # Enclosing scope(外层作用域)
    len = "外层函数的 len"
    
    def inner_function():
#        # Local scope(局部作用域)
        len = "局部的 len"
        print(f"局部作用域中的 len:{len}")
        
#        # 访问不同作用域的变量
        def show_scopes():
            print(f"当前 len:{len}")  # 局部的 len
    
    inner_function()
    print(f"外层作用域中的 len:{len}")

demonstrate_legb()
print(f"全局作用域中的 len:{len}")

## 恢复内置的 len 函数
del len
print(f"内置的 len 函数:{len([1, 2, 3])}")  # 现在又可以正常使用了

global 关键字

基本用法

global关键字用于在函数内部声明全局变量。

## 全局变量
counter = 0
user_name = "未设置"

def increment_counter():
    """增加计数器"""
    global counter
    counter += 1
    print(f"计数器值:{counter}")

def set_user_name(name):
    """设置用户名"""
    global user_name
    user_name = name
    print(f"用户名已设置为:{user_name}")

def get_user_info():
    """获取用户信息"""
#    # 只读取全局变量,不需要 global 声明
    print(f"当前用户:{user_name},访问次数:{counter}")

## 使用示例
print(f"初始计数器:{counter}")
increment_counter()
increment_counter()
set_user_name("张三")
get_user_info()

复杂示例

## 配置管理示例
config = {
    "debug": False,
    "max_connections": 100,
    "timeout": 30
}

log_level = "INFO"

def update_config(key, value):
    """更新配置"""
    global config
    if key in config:
        old_value = config[key]
        config[key] = value
        print(f"配置更新:{key}{old_value} 改为 {value}")
    else:
        print(f"未知配置项:{key}")

def set_debug_mode(enabled):
    """设置调试模式"""
    global config, log_level
    config["debug"] = enabled
    log_level = "DEBUG" if enabled else "INFO"
    print(f"调试模式:{'开启' if enabled else '关闭'},日志级别:{log_level}")

def get_config_summary():
    """获取配置摘要"""
    print(f"当前配置:{config}")
    print(f"日志级别:{log_level}")

## 使用示例
get_config_summary()
update_config("max_connections", 200)
set_debug_mode(True)
get_config_summary()

注意事项

## 常见错误示例
x = 10

def problematic_function():
    """演示常见的 global 使用错误"""
    print(x)  # 这行可以正常执行
#    # x += 1  # 这行会报错:UnboundLocalError
#    # 因为+=操作既读取又赋值,Python 认为 x 是局部变量

def correct_function():
    """正确的做法"""
    global x
    print(x)
    x += 1
    print(f"x 增加后:{x}")

## 演示
print(f"初始 x:{x}")
problematic_function()  # 只打印,不修改
correct_function()      # 正确修改全局变量
print(f"最终 x:{x}")

## 另一个常见错误
y = [1, 2, 3]

def modify_list():
    """修改可变对象"""
#    # 对于可变对象,如果只是修改内容(不重新赋值),不需要 global
    y.append(4)
    print(f"修改后的列表:{y}")

def replace_list():
    """替换整个列表"""
    global y
    y = [10, 20, 30]
    print(f"替换后的列表:{y}")

modify_list()   # 修改内容
replace_list()  # 替换整个对象

nonlocal 关键字

基本概念

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

def create_counter():
    """创建一个计数器函数"""
    count = 0
    
    def increment():
        nonlocal count
        count += 1
        return count
    
    def decrement():
        nonlocal count
        count -= 1
        return count
    
    def get_count():
#        # 只读取,不需要 nonlocal
        return count
    
    def reset():
        nonlocal count
        count = 0
        return count
    
#    # 返回操作函数
    return {
        "increment": increment,
        "decrement": decrement,
        "get_count": get_count,
        "reset": reset
    }

## 使用示例
counter1 = create_counter()
print(f"初始计数:{counter1['get_count']()}")  # 0
print(f"增加后:{counter1['increment']()}")      # 1
print(f"再增加:{counter1['increment']()}")      # 2
print(f"减少后:{counter1['decrement']()}")      # 1
print(f"重置后:{counter1['reset']()}")         # 0

## 创建另一个独立的计数器
counter2 = create_counter()
print(f"计数器 2:{counter2['increment']()}")     # 1
print(f"计数器 1:{counter1['get_count']()}")    # 0(独立的)

高级应用

def create_bank_account(initial_balance=0):
    """创建银行账户"""
    balance = initial_balance
    transaction_history = []
    
    def deposit(amount):
        """存款"""
        nonlocal balance
        if amount > 0:
            balance += amount
            transaction_history.append(f"存款:+{amount},余额:{balance}")
            return True
        return False
    
    def withdraw(amount):
        """取款"""
        nonlocal balance
        if 0 < amount <= balance:
            balance -= amount
            transaction_history.append(f"取款:-{amount},余额:{balance}")
            return True
        return False
    
    def get_balance():
        """查询余额"""
        return balance
    
    def get_history():
        """查询交易历史"""
        return transaction_history.copy()
    
    def transfer_to(target_account, amount):
        """转账到其他账户"""
        if withdraw(amount):
            if target_account['deposit'](amount):
                transaction_history.append(f"转出:-{amount},余额:{balance}")
                return True
            else:
#                # 如果目标账户存款失败,回滚
                deposit(amount)
                transaction_history.pop()  # 移除取款记录
        return False
    
    return {
        "deposit": deposit,
        "withdraw": withdraw,
        "get_balance": get_balance,
        "get_history": get_history,
        "transfer_to": transfer_to
    }

## 使用示例
account1 = create_bank_account(1000)
account2 = create_bank_account(500)

print(f"账户 1 余额:{account1['get_balance']()}")
print(f"账户 2 余额:{account2['get_balance']()}")

## 存取款操作
account1['deposit'](200)
account1['withdraw'](150)

## 转账操作
if account1['transfer_to'](account2, 300):
    print("转账成功")
else:
    print("转账失败")

print(f"\n 账户 1 余额:{account1['get_balance']()}")
print(f"账户 2 余额:{account2['get_balance']()}")

print("\n 账户 1 交易历史:")
for record in account1['get_history']():
    print(f"  {record}")

闭包

闭包的概念

闭包是指内层函数引用了外层函数的变量,即使外层函数已经执行完毕,这些变量仍然被保持。

def create_multiplier(factor):
    """创建乘法器闭包"""
    def multiply(number):
#        # 这里引用了外层函数的 factor 变量
        return number * factor
    
    return multiply

## 创建不同的乘法器
double = create_multiplier(2)
triple = create_multiplier(3)
tenfold = create_multiplier(10)

## 使用闭包
print(f"5 的两倍:{double(5)}")      # 10
print(f"5 的三倍:{triple(5)}")      # 15
print(f"5 的十倍:{tenfold(5)}")     # 50

## 每个闭包都保持着自己的 factor 值
print(f"double 的 factor:{double.__closure__[0].cell_contents}")
print(f"triple 的 factor:{triple.__closure__[0].cell_contents}")

闭包的实际应用

def create_validator(validation_rule):
    """创建验证器闭包"""
    def validate(value):
        return validation_rule(value)
    
    return validate

## 创建不同的验证器
is_positive = create_validator(lambda x: x > 0)
is_even = create_validator(lambda x: x % 2 == 0)
is_in_range = create_validator(lambda x: 1 <= x <= 100)

## 使用验证器
test_values = [-5, 0, 2, 15, 150]

for value in test_values:
    print(f"值 {value}:")
    print(f"  是正数:{is_positive(value)}")
    print(f"  是偶数:{is_even(value)}")
    print(f"  在范围内:{is_in_range(value)}")
    print()

def create_cache():
    """创建缓存闭包"""
    cache = {}
    
    def cached_function(func):
        def wrapper(*args, **kwargs):
#            # 创建缓存键
            key = str(args) + str(sorted(kwargs.items()))
            
            if key in cache:
                print(f"缓存命中:{key}")
                return cache[key]
            
#            # 计算结果并缓存
            result = func(*args, **kwargs)
            cache[key] = result
            print(f"缓存存储:{key} -> {result}")
            return result
        
        return wrapper
    
    def get_cache_info():
        return {
            "size": len(cache),
            "keys": list(cache.keys())
        }
    
    def clear_cache():
        cache.clear()
        print("缓存已清空")
    
    return cached_function, get_cache_info, clear_cache

## 使用缓存闭包
cached_decorator, get_info, clear = create_cache()

@cached_decorator
def expensive_calculation(n):
    """模拟耗时计算"""
    import time
    time.sleep(0.1)  # 模拟计算时间
    return n ** 2

## 测试缓存效果
print("第一次计算:")
result1 = expensive_calculation(5)
print(f"结果:{result1}")

print("\n 第二次计算(相同参数):")
result2 = expensive_calculation(5)
print(f"结果:{result2}")

print(f"\n 缓存信息:{get_info()}")

默认参数的作用域

可变默认参数的陷阱

## 错误示例:可变默认参数
def add_item_wrong(item, target_list=[]):
    """错误的默认参数使用"""
    target_list.append(item)
    return target_list

## 演示问题
list1 = add_item_wrong("第一个")
print(f"第一次调用:{list1}")  # ['第一个']

list2 = add_item_wrong("第二个")
print(f"第二次调用:{list2}")  # ['第一个', '第二个'] - 意外!

list3 = add_item_wrong("第三个")
print(f"第三次调用:{list3}")  # ['第一个', '第二个', '第三个'] - 更意外!

## 查看默认参数
print(f"函数默认参数:{add_item_wrong.__defaults__}")

## 正确的做法 1:使用 None 作为默认值
def add_item_correct1(item, target_list=None):
    """正确的默认参数使用方法 1"""
    if target_list is None:
        target_list = []
    target_list.append(item)
    return target_list

## 正确的做法 2:使用影子拷贝
def add_item_correct2(item, target_list=None):
    """正确的默认参数使用方法 2"""
    if target_list is None:
        target_list = []
    else:
        target_list = target_list.copy()  # 创建副本
    target_list.append(item)
    return target_list

## 测试正确的实现
print("\n 正确实现的测试:")
list_a = add_item_correct1("A")
list_b = add_item_correct1("B")
print(f"列表 A:{list_a}")  # ['A']
print(f"列表 B:{list_b}")  # ['B']

默认参数的最佳实践

def create_user_profile(name, age, hobbies=None, settings=None):
    """创建用户档案的最佳实践"""
#    # 使用 None 作为可变对象的默认值
    if hobbies is None:
        hobbies = []
    if settings is None:
        settings = {"theme": "light", "notifications": True}
    
    profile = {
        "name": name,
        "age": age,
        "hobbies": hobbies.copy(),  # 创建副本避免意外修改
        "settings": settings.copy()
    }
    
    return profile

def log_message(message, timestamp=None, level="INFO"):
    """日志记录函数"""
    if timestamp is None:
        import datetime
        timestamp = datetime.datetime.now()
    
    log_entry = f"[{timestamp}] {level}: {message}"
    print(log_entry)
    return log_entry

## 使用示例
user1 = create_user_profile("张三", 25, ["读书", "游泳"])
user2 = create_user_profile("李四", 30)  # 使用默认值

print(f"用户 1:{user1}")
print(f"用户 2:{user2}")

## 修改 user1 的爱好不会影响 user2
user1["hobbies"].append("编程")
print(f"修改后用户 1:{user1['hobbies']}")
print(f"用户 2 仍然是:{user2['hobbies']}")

## 日志示例
log_message("系统启动")
log_message("用户登录", level="DEBUG")
log_message("错误发生", level="ERROR")

函数的销毁和生命周期

全局函数的销毁

## 演示函数的销毁
def original_function():
    """原始函数"""
    return "我是原始函数"

print(f"调用原始函数:{original_function()}")

## 方法 1:重新定义同名函数
def original_function():
    """重新定义的函数"""
    return "我是新函数"

print(f"调用新函数:{original_function()}")

## 方法 2:使用 del 删除函数
def temp_function():
    return "临时函数"

print(f"临时函数存在:{temp_function()}")
del temp_function

try:
    temp_function()
except NameError as e:
    print(f"函数已删除:{e}")

## 方法 3:程序结束时自动销毁(这里只是演示概念)
print("程序结束时,所有函数都会被销毁")

局部函数和闭包的生命周期

def demonstrate_function_lifecycle():
    """演示函数生命周期"""
    
    def create_closure_with_data():
        data = [1, 2, 3, 4, 5]
        
        def inner_function():
            return sum(data)
        
        return inner_function
    
#    # 创建闭包
    closure_func = create_closure_with_data()
    print(f"闭包结果:{closure_func()}")
    
#    # 即使外层函数执行完毕,闭包仍然保持对 data 的引用
    print(f"闭包仍然有效:{closure_func()}")
    
#    # 删除闭包引用
    del closure_func
    print("闭包引用已删除")
    
#    # 演示局部函数的销毁
    def outer_with_inner():
        def inner():
            return "内层函数"
        
#        # 在外层函数内部可以调用
        result = inner()
        return result
    
    result = outer_with_inner()
    print(f"外层函数结果:{result}")
    
#    # 外层函数执行完毕后,内层函数也被销毁
#    # inner()  # 这会引发 NameError

demonstrate_function_lifecycle()

💡 实际应用

配置管理器

def create_config_manager():
    """创建配置管理器"""
    _config = {}
    _defaults = {}
    _validators = {}
    
    def set_default(key, value, validator=None):
        """设置默认值"""
        nonlocal _defaults, _validators
        _defaults[key] = value
        if validator:
            _validators[key] = validator
    
    def set_config(key, value):
        """设置配置值"""
        nonlocal _config
        
#        # 验证值
        if key in _validators:
            if not _validators[key](value):
                raise ValueError(f"配置值 {key}={value} 验证失败")
        
        _config[key] = value
    
    def get_config(key, default=None):
        """获取配置值"""
        if key in _config:
            return _config[key]
        elif key in _defaults:
            return _defaults[key]
        else:
            return default
    
    def get_all_config():
        """获取所有配置"""
        result = _defaults.copy()
        result.update(_config)
        return result
    
    def reset_config(key=None):
        """重置配置"""
        nonlocal _config
        if key:
            _config.pop(key, None)
        else:
            _config.clear()
    
    return {
        "set_default": set_default,
        "set_config": set_config,
        "get_config": get_config,
        "get_all_config": get_all_config,
        "reset_config": reset_config
    }

## 使用配置管理器
config_mgr = create_config_manager()

## 设置默认值和验证器
config_mgr["set_default"]("max_connections", 100, lambda x: isinstance(x, int) and x > 0)
config_mgr["set_default"]("timeout", 30, lambda x: isinstance(x, (int, float)) and x > 0)
config_mgr["set_default"]("debug", False, lambda x: isinstance(x, bool))

## 设置配置
config_mgr["set_config"]("max_connections", 200)
config_mgr["set_config"]("debug", True)

print("当前配置:")
for key, value in config_mgr["get_all_config"]().items():
    print(f"  {key}: {value}")

## 尝试设置无效值
try:
    config_mgr["set_config"]("max_connections", -1)
except ValueError as e:
    print(f"配置错误:{e}")

事件系统

def create_event_system():
    """创建事件系统"""
    _listeners = {}
    _event_history = []
    
    def add_listener(event_type, callback):
        """添加事件监听器"""
        nonlocal _listeners
        if event_type not in _listeners:
            _listeners[event_type] = []
        _listeners[event_type].append(callback)
    
    def remove_listener(event_type, callback):
        """移除事件监听器"""
        nonlocal _listeners
        if event_type in _listeners:
            try:
                _listeners[event_type].remove(callback)
                if not _listeners[event_type]:
                    del _listeners[event_type]
            except ValueError:
                pass
    
    def emit_event(event_type, data=None):
        """触发事件"""
        nonlocal _event_history
        
#        # 记录事件历史
        import datetime
        event_record = {
            "type": event_type,
            "data": data,
            "timestamp": datetime.datetime.now(),
            "listeners_count": len(_listeners.get(event_type, []))
        }
        _event_history.append(event_record)
        
#        # 调用所有监听器
        if event_type in _listeners:
            for callback in _listeners[event_type]:
                try:
                    callback(data)
                except Exception as e:
                    print(f"事件监听器错误:{e}")
    
    def get_listeners(event_type=None):
        """获取监听器信息"""
        if event_type:
            return _listeners.get(event_type, [])
        return _listeners.copy()
    
    def get_event_history(limit=None):
        """获取事件历史"""
        if limit:
            return _event_history[-limit:]
        return _event_history.copy()
    
    def clear_history():
        """清空事件历史"""
        nonlocal _event_history
        _event_history.clear()
    
    return {
        "add_listener": add_listener,
        "remove_listener": remove_listener,
        "emit_event": emit_event,
        "get_listeners": get_listeners,
        "get_event_history": get_event_history,
        "clear_history": clear_history
    }

## 使用事件系统
event_system = create_event_system()

## 定义事件处理函数
def on_user_login(data):
    print(f"用户登录:{data['username']}")

def on_user_logout(data):
    print(f"用户登出:{data['username']}")

def log_all_events(data):
    print(f"事件日志:{data}")

## 注册事件监听器
event_system["add_listener"]("user_login", on_user_login)
event_system["add_listener"]("user_logout", on_user_logout)
event_system["add_listener"]("user_login", log_all_events)
event_system["add_listener"]("user_logout", log_all_events)

## 触发事件
event_system["emit_event"]("user_login", {"username": "张三", "ip": "192.168.1.100"})
event_system["emit_event"]("user_logout", {"username": "张三"})

## 查看事件历史
print("\n 事件历史:")
for event in event_system["get_event_history"]():
    print(f"  {event['timestamp']}: {event['type']} (监听器: {event['listeners_count']})")

⚠️ 注意事项

  • 避免过度使用 global: 全局变量会增加代码的复杂性和耦合度
  • 谨慎使用可变默认参数: 使用 None 作为默认值,在函数内部创建可变对象
  • 理解闭包的内存影响: 闭包会保持对外层变量的引用,可能影响垃圾回收
  • nonlocal 的限制: nonlocal 只能用于嵌套函数,不能用于全局作用域
  • 作用域查找性能: 访问局部变量比全局变量更快
  • 避免循环引用: 在闭包中要注意避免创建循环引用

🔗 相关内容

📚 扩展阅读

🏷️ 标签

作用域 闭包 global nonlocal 变量作用域 LEGB 函数生命周期


最后更新: 2024-01-01
作者: Python 文档团队
版本: 1.0

作者: Python 文档团队

版本: 1.0

讨论与反馈

欢迎在下方留言讨论,分享你的学习心得或提出问题。评论基于GitHub Issues,需要GitHub账号。